Auto-link commits, branches, and PRs to issues via post-receive hook (SZ-122)
rk@tigase.net opened 3 weeks ago

Summary

Automatically link commits, branches, and PRs to Sztab issues when an issue key appears in a commit message, branch name, or PR title. Eliminates manual traceability workarounds and makes Sztab's repository and project management layers first-class citizens of each other.

Motivation

Raised by Wojciech: inter-item linking (commits ↔ issues ↔ PRs) is a core feature of serious project management software. OneDev had it and removed it for no good reason. Sztab will have it from the start.

Issue key format

Canonical Sztab key: SZTaa00001 (two lowercase project-code letters + 5 digits) Regex: SZT[a-z]{2}\d{5}

Acceptance criteria

  •  post-receive hook installed on repo creation
  •  Hook fires on every push and calls Sztabina internal endpoint
  •  Commit messages and branch names parsed for SZT[a-z]{2}\d{5}
  •  Links persisted with duplicate guard
  •  Issue view shows linked commits and PRs
  •  PR view shows linked issue badge
  •  Manual link from issue UI works
  •  Existing repos migrated via hook install script
  •  No regressions to push, PR, or issue workflows
  • rk@tigase.net commented 3 weeks ago

    Design

    Trigger: git post-receive hook

    • Installed into every bare repo under hooks/post-receive at creation time
    • One-line addition to RepoHandler.CreateRepo in Sztabina
    • Migration script installs hook into all existing repos
    • Hook reads stdin (oldSha newSha refName), fetches commit messages via git log, calls POST /internal/notify/push on Sztabina

    Sztabina: new internal endpoint

    • POST /internal/notify/push
    • Receives push data, extracts commits and branch name
    • Fires POST /api/internal/hooks/push on Spring backend

    Spring backend: IssueReferenceService

    • Parses issue keys from commit messages and branch name
    • Resolves keys to issue IDs via existing global key lookup
    • Persists to issue_references table
    • Duplicate guard: upsert on (issue_id, commit_sha, source_type)

    PR linking

    • On PR create/update: parse PR title for issue keys
    • Same IssueReferenceService, source_type = PR

    Manual linking

    • Issue UI: "Link" button → search/select issue => source_type = MANUAL

    New table: issue_references

    ColumnTypeNotes
    idbigserialPK
    issue_idbigintFK => issues
    project_idbigintFK => projects
    commit_shavarchar(40)nullable
    pr_idbigintFK => pull_requests, nullable
    source_typevarcharCOMMIT, BRANCH, PR, MANUAL
    ref_textvarcharraw key found e.g. SZTsz00121
    created_attimestamptz

    Visibility

    • Issue view: new "References" section showing:
      • Linked commits (sha, message, author, date)
      • Linked PRs (title, status, author)
    • PR view: linked issue badge in PR header

    Flyway migration

    • V16__add_issue_references.sql

    Out of scope

    • Automatic status transitions on PR merge — separate ticket, policy is complex
    • Commit detail page — follow-up
  • rk@tigase.net commented 3 weeks ago

    Task breakdown

    • post-receive hook script + install on create: 2h
    • Sztabina /internal/notify/push endpoint: 3h
    • Spring IssueReferenceService + V16 migration: 4h
    • PR title parsing on create/update: 2h
    • Issue view References section (frontend): 6h
    • PR view linked issue badge (frontend): 2h
    • Manual link UI: 3h
    • Existing repo migration script: 1h
    • Testing + regression: 1h
  • Wojciech Kapcia (Tigase) commented 3 weeks ago

    On 19/03/2026 21:23, rk@tigase.net wrote:

    I think this will be valuable - I recall you raised this before.

    Good memory, I don't remember mentioning it :)

    This is my approach to automatic commit <=> issue <=> PR linking using Sztabina push webhooks. Pl let me know if I am overcomplicating:

    • On every push, Sztabina fires a webhook to the backend
    • Commit messages and branch names are parsed for Sztab issue keys (SZTaa00001 format)
    • Matching issues are automatically linked and shown in the issue view
    • PR titles are also parsed on creation/update
    • Manual linking from the issue UI as a fallback

    Thoughts?

    Seems about right.

    One problem I see is matching the issues from the commit message - over the years we used so many systems that the issue format differed a lot from one to another: from the base #<issue_number> to #<[short-]project-ID>-number to full-blown#-issue-id (e.g. #tigase/_clients/beagle-im#654 or #SZ-122 or #122 )

    Sztab will probably have its own ID system so we should think how (if we want past commits association) do that.

    Ps: Why not take it a step further with automatic issue status transitions driven by PR state transitions: if a PR title references an issue key and the PR is merged, the issue automatically transitions to RESOLVED. keywords — for example, a commit message containing "fixes SZTsz00121" would automatically move that issue to RESOLVED via the existing state machine. Conversely, when the PR is reopened (APPROVED => IN PROGRESS), the issue goes from R -> O. But this gets complicated as there could be more than one PR addressing an issue - Dobra, nie mieszam, bo się jeszcze zsiądzie.

    Some systems do that though, to be honest, I never used that nor was convinced by it. I guess one of the reasons is/was because Tigase is quite modular and quite often you had to make changes in multiple repositories then changes from one could trigger issue state while not finally fixing something. But it could be usefull.

  • rk@tigase.net commented 3 weeks ago

    Wojciech,

    You asked for issues and pull requests to be linked to each other. Here is what that will look like in Sztab.

    Linking happens automatically. Whenever an issue key (e.g. SZTab00001) appears in a commit message, a branch name, or a PR title, Sztab picks it up and creates the link. You do not have to do anything.

    From the issue side, a new Linked Items tab will appear in the issue modal, next to Comments and Time. It shows all the branches, commits, and pull requests that mention that issue — as they accumulate over the life of the issue.

    From the PR side, any issues associated with that PR appear as small clickable badges near the PR title. Clicking a badge takes you straight to the issue.

    If a link was created by mistake — for example because a commit referenced the wrong issue key — it can be removed manually from the issue screen. You can also add links manually if the automatic detection did not pick something up.

    Screenshots attached show where these will appear in the UI.

    Does this match what you had in mind?

    Rk

    Items linked to Issues: Screenshot 2026-03-22 at 5.04.49 PM.pngScreenshot 2026-03-22 at 5.04.49 PM

    Items linked to PullRequests: Screenshot 2026-03-22 at 9.33.32 PM_3.pngScreenshot 2026-03-22 at 9.33.32 PM

  • rk@tigase.net commented 3 weeks ago
    rksuma@Ramakrishnans-MacBook-Pro sztab % git checkout -b feature/SZ-122-LinkedItems
    Switched to a new branch 'feature/SZ-122-LinkedItems'
    rksuma@Ramakrishnans-MacBook-Pro sztab % 
    
    
  • rk@tigase.net changed state to 'In Progress' 3 weeks ago
    Previous Value Current Value
    Open
    In Progress
  • rk@tigase.net commented 3 weeks ago

    One problem I see is matching the issues from the commit message - over the years we used so many systems that the issue format differed a lot from one to another: from the base #<issue_number> to #<[short-]project-ID>-number to full-blown#-issue-id (e.g. #tigase/_clients/beagle-im#654 or #SZ-122 or #122 )

    Past commits in migrated repos will have #654, #SZ-122, #tigase/_clients/beagle-im#654 — none of which match SZT[a-z]{2}\d{5}. For SZ-122 this is explicitly out of scope (forward-looking only), but it should go in the backlog as SZ-122f — a one-time historical commit scan with a pluggable key format adapter.

    I hope this is not a deal-breaker.

  • rk@tigase.net commented 3 weeks ago

    Artur,

    You wanted a release. I would like to include this feature in code drop 1.10.0. I can get this implemented and tested by Tuesday end and release 1.10.0 Wed AM.

    I hope this is acceptable.

  • rk@tigase.net commented 3 weeks ago

    Hook-based Auto-Linking Design

    Flow

    1. git push

      • Handled by git-http-backend (CGI)
      • Push is accepted
    2. post-receive hook

      • Triggered automatically by Git
      • Reads stdin:
        <old-sha> <new-sha> <ref>
        
      • Invokes:
        sztabina notify --repo <repo> --old <old> --new <new> --ref <ref> &
        
      • Runs asynchronously => does not block push
    3. Sztabina (notify mode)

      • Parses CLI args: --repo, --old, --new, --ref
      • Opens repo via LocalGitEngine
      • Calls ListCommitsBetween(old, new)
      • Builds payload:
        • repo name
        • ref (branch)
        • commit list (sha, message, metadata)
      • POSTs to:
        /internal/hooks/receive
        
      • Retry with timeout (--connect-timeout 3, --max-time 5)
    4. Spring Boot

      • HookReceiveController
        • Validates shared secret
        • Calls IssueLinkService.processHook(...)
    5. IssueLinkService

      • Resolve project via projectRepository.findByName(repoName)
      • Extract issue keys from:
        • branch name (ref)
        • commit messages
      • Upsert into:
        • issue_commit_links
        • issue_branch_links
    6. Existing repos

      • sztabina install-hooks
      • Walks GIT_REPO_ROOT
      • Installs post-receive hook in each repo

    Flow of data across three domains: git client, Sztabina and Sztab:

    User triggers a git push using git client, which causes Sztabina to trigger the hook, which in turn enriches the content and calls IssueLinkService to link the commit to the issue.

    Screenshot 2026-03-23 at 4.18.38 PM.pngScreenshot 2026-03-23 at 4.18.38 PM

  • rk@tigase.net commented 3 weeks ago
  • rk@tigase.net changed state to 'Pending approval' 3 weeks ago
    Previous Value Current Value
    In Progress
    Pending approval
  • rk@tigase.net commented 3 weeks ago
    rksuma@Ramakrishnans-MacBook-Pro sztab % git fetch origin
    git rebase origin/wolnosc
    Current branch feature/SZ-122-LinkedItems is up to date.
    rksuma@Ramakrishnans-MacBook-Pro sztab % git checkout wolnosc
    git pull origin wolnosc
    Switched to branch 'wolnosc'
    Your branch is up to date with 'origin/wolnosc'.
    From https://tigase.dev/sztab
     * branch            wolnosc    -> FETCH_HEAD
    Already up to date.
    rksuma@Ramakrishnans-MacBook-Pro sztab % git merge --ff-only feature/SZ-122-LinkedItems
    Updating a6b16ef..ecbecaf
    Fast-forward
     backend/src/main/java/com/sztab/controller/hook/HookReceiveController.java     |  43 +++++++++++++++++
     backend/src/main/java/com/sztab/controller/link/IssueLinkController.java       |  69 ++++++++++++++++++++++++++++
     backend/src/main/java/com/sztab/controller/link/PrLinkController.java          |  35 ++++++++++++++
     backend/src/main/java/com/sztab/dto/hook/HookReceivePayload.java               |  20 ++++++++
     backend/src/main/java/com/sztab/dto/link/IssueBranchLinkDto.java               |  22 +++++++++
     backend/src/main/java/com/sztab/dto/link/IssueCommitLinkDto.java               |  28 ++++++++++++
     backend/src/main/java/com/sztab/dto/link/IssuePrLinkDto.java                   |  28 ++++++++++++
     backend/src/main/java/com/sztab/hooks/IssueKeyExtractor.java                   |  42 +++++++++++++++++
     backend/src/main/java/com/sztab/model/link/IssueBranchLink.java                |  34 ++++++++++++++
     backend/src/main/java/com/sztab/model/link/IssueCommitLink.java                |  48 +++++++++++++++++++
     backend/src/main/java/com/sztab/model/link/IssuePrLink.java                    |  34 ++++++++++++++
     backend/src/main/java/com/sztab/repository/link/IssueBranchLinkRepository.java |  28 ++++++++++++
     backend/src/main/java/com/sztab/repository/link/IssueCommitLinkRepository.java |  35 ++++++++++++++
     backend/src/main/java/com/sztab/repository/link/IssuePrLinkRepository.java     |  30 ++++++++++++
     backend/src/main/java/com/sztab/security/config/OidcSecurityConfig.java        |   8 +++-
     backend/src/main/java/com/sztab/security/config/SessionAuthSecurityConfig.java |   3 +-
     backend/src/main/java/com/sztab/service/impl/PullRequestServiceImpl.java       |  20 ++++++--
     backend/src/main/java/com/sztab/service/impl/link/IssueLinkServiceImpl.java    | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++
     backend/src/main/java/com/sztab/service/link/IssueLinkService.java             |  26 +++++++++++
     backend/src/main/resources/application.yml                                     |   2 +
     backend/src/main/resources/db/migration/V22__issue_auto_links.sql              |  38 +++++++++++++++
     backend/src/test/java/com/sztab/hooks/IssueKeyExtractorTest.java               |  99 +++++++++++++++++++++++++++++++++++++++
     backend/src/test/java/com/sztab/service/PullRequestServiceImplTest.java        |  10 ++--
     deploy/docker/docker-compose.yml                                               |   3 ++
     frontend/src/api/links.ts                                                      |  39 ++++++++++++++++
     frontend/src/components/issues/IssueEditModal.tsx                              |  17 ++++++-
     frontend/src/components/issues/IssueLinkedItems.tsx                            | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     frontend/src/components/issues/ManualLinkModal.tsx                             | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++
     frontend/src/components/pr/LinkedIssueBadge.tsx                                |  36 +++++++++++++++
     frontend/src/pages/pr/PrDetailPage.tsx                                         |   3 ++
     frontend/src/types/links.ts                                                    |  31 +++++++++++++
     sztabina/cmd/notify/notify.go                                                  | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     sztabina/cmd/sztabina/main.go                                                  |  51 +++++++++++++++++++++
     sztabina/exec/engine.go                                                        |  16 +++++++
     sztabina/handlers/repo_handler.go                                              |  12 +++++
     sztabina/hooks/hook_writer.go                                                  |  45 ++++++++++++++++++
     36 files changed, 1549 insertions(+), 10 deletions(-)
     create mode 100644 backend/src/main/java/com/sztab/controller/hook/HookReceiveController.java
     create mode 100644 backend/src/main/java/com/sztab/controller/link/IssueLinkController.java
     create mode 100644 backend/src/main/java/com/sztab/controller/link/PrLinkController.java
     create mode 100644 backend/src/main/java/com/sztab/dto/hook/HookReceivePayload.java
     create mode 100644 backend/src/main/java/com/sztab/dto/link/IssueBranchLinkDto.java
     create mode 100644 backend/src/main/java/com/sztab/dto/link/IssueCommitLinkDto.java
     create mode 100644 backend/src/main/java/com/sztab/dto/link/IssuePrLinkDto.java
     create mode 100644 backend/src/main/java/com/sztab/hooks/IssueKeyExtractor.java
     create mode 100644 backend/src/main/java/com/sztab/model/link/IssueBranchLink.java
     create mode 100644 backend/src/main/java/com/sztab/model/link/IssueCommitLink.java
     create mode 100644 backend/src/main/java/com/sztab/model/link/IssuePrLink.java
     create mode 100644 backend/src/main/java/com/sztab/repository/link/IssueBranchLinkRepository.java
     create mode 100644 backend/src/main/java/com/sztab/repository/link/IssueCommitLinkRepository.java
     create mode 100644 backend/src/main/java/com/sztab/repository/link/IssuePrLinkRepository.java
     create mode 100644 backend/src/main/java/com/sztab/service/impl/link/IssueLinkServiceImpl.java
     create mode 100644 backend/src/main/java/com/sztab/service/link/IssueLinkService.java
     create mode 100644 backend/src/main/resources/db/migration/V22__issue_auto_links.sql
     create mode 100644 backend/src/test/java/com/sztab/hooks/IssueKeyExtractorTest.java
     create mode 100644 frontend/src/api/links.ts
     create mode 100644 frontend/src/components/issues/IssueLinkedItems.tsx
     create mode 100644 frontend/src/components/issues/ManualLinkModal.tsx
     create mode 100644 frontend/src/components/pr/LinkedIssueBadge.tsx
     create mode 100644 frontend/src/types/links.ts
     create mode 100644 sztabina/cmd/notify/notify.go
     create mode 100644 sztabina/hooks/hook_writer.go
    rksuma@Ramakrishnans-MacBook-Pro sztab % git push origin wolnosc
    Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
    To https://tigase.dev/sztab.git
       a6b16ef..ecbecaf  wolnosc -> wolnosc
    rksuma@Ramakrishnans-MacBook-Pro sztab % git branch -d feature/SZ-122-LinkedItems
    git push origin --delete feature/SZ-122-LinkedItems
    Deleted branch feature/SZ-122-LinkedItems (was ecbecaf).
    remote:  
    remote: Create a pull request for 'feature/SZ-122-LinkedItems' by visiting:
    remote:     https://tigase.dev/sztab/~pulls/new?target=1325:wolnosc&source=1325:feature/SZ-122-LinkedItems
    remote:  
    To https://tigase.dev/sztab.git
     - [deleted]         feature/SZ-122-LinkedItems
    rksuma@Ramakrishnans-MacBook-Pro sztab %
    
  • rk@tigase.net changed state to 'Closed' 3 weeks ago
    Previous Value Current Value
    Pending approval
    Closed
  • Wojciech Kapcia (Tigase) commented 3 weeks ago

    Wojciech,

    Btw. you can user-mention with @ in issues -- you don't have to send explicit emails :)

    Does this match what you had in mind?

    From the screenshots it seems to look OK.

    Though I'm not sure about the actual issue IDs. (SZT[a-z]{2}\d{5}) is mentioned in the PR (and below) and as example SZTab00001 -- could you elaborate on issue IDs? Are they actually upper-lower-case mixed and the lower-case serves as a separator somehow?

    Past commits in migrated repos will have #654, #SZ-122, #tigase/_clients/beagle-im#654 — none of which match SZT[a-z]{2}\d{5}. For SZ-122 this is explicitly out of scope (forward-looking only), but it should go in the backlog as SZ-122f — a one-time historical commit scan with a pluggable key format adapter.

    I hope this is not a deal-breaker.

    This is totally fine and can be done at a later time during one-time scan (though I would create another "feature request" issue so it won't get lost)

  • rk@tigase.net commented 3 weeks ago

    Though I'm not sure about the actual issue IDs. (SZT[a-z]{2}\d{5}) is mentioned in the PR (and below) and as example SZTab00001 -- could you elaborate on issue IDs? Are they actually upper-lower-case mixed and the lower-case serves as a separator somehow?

    The key format is documented in the issue management doc, section 1.4. The ORG prefix is whatever sztab.org.code is configured to at installation time — for the test env that's SZT."

  • rk@tigase.net referenced from other issue 1 day ago
issue 1 of 1
Type
New Feature
Priority
Normal
Assignee
Version
1.10.0
Sprints
n/a
Customer
n/a
Issue Votes (0)
Watchers (3)
Reference
SZ-122
Please wait...
Page is in error, reload to recover