-
Design
Trigger: git post-receive hook
- Installed into every bare repo under
hooks/post-receiveat creation time - One-line addition to
RepoHandler.CreateRepoin Sztabina - Migration script installs hook into all existing repos
- Hook reads stdin (oldSha newSha refName), fetches commit messages via
git log, callsPOST /internal/notify/pushon Sztabina
Sztabina: new internal endpoint
POST /internal/notify/push- Receives push data, extracts commits and branch name
- Fires
POST /api/internal/hooks/pushon 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_referencestable - 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_referencesColumn Type Notes id bigserial PK issue_id bigint FK => issues project_id bigint FK => projects commit_sha varchar(40) nullable pr_id bigint FK => pull_requests, nullable source_type varchar COMMIT, BRANCH, PR, MANUAL ref_text varchar raw key found e.g. SZTsz00121created_at timestamptz 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
- Installed into every bare repo under
-
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
-
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.
-
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 PMItems linked to PullRequests:
Screenshot 2026-03-22 at 9.33.32 PM -
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.
-
Hook-based Auto-Linking Design
Flow
-
git push
- Handled by
git-http-backend(CGI) - Push is accepted
- Handled by
-
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
-
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)
- Parses CLI args:
-
Spring Boot
HookReceiveController- Validates shared secret
- Calls
IssueLinkService.processHook(...)
-
IssueLinkService
- Resolve project via
projectRepository.findByName(repoName) - Extract issue keys from:
- branch name (ref)
- commit messages
- Upsert into:
issue_commit_linksissue_branch_links
- Resolve project via
-
Existing repos
sztabina install-hooks- Walks
GIT_REPO_ROOT - Installs
post-receivehook 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 -
-
-
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 % -
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 exampleSZTab00001-- 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)
-
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 exampleSZTab00001-- 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."
-
| Type |
New Feature
|
| Priority |
Normal
|
| Assignee | |
| Version |
1.10.0
|
| Sprints |
n/a
|
| Customer |
n/a
|
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
SZT[a-z]{2}\d{5}