Skip to content

Consolidate the SITMUN repositories into a new sitmun/sitmun monorepo #105

Open
2 of 9 issues completed
Open

Description

@fjlopez

In short

SITMUN lives in nine GitHub repositories today. This proposal creates one new repository, sitmun/sitmun, with one build and one version for everything that runs in Docker. The two mobile apps keep their own versions.

sitmun/sitmun is a brand new repository. It is not a rename of sitmun-application-stack. It starts from a snapshot import of all nine repositories, including this one.

The structural refactor starts in sitmun/sitmun right after the import. The old repositories stay writable for about one month. Work already in flight lands there and is ported into sitmun/sitmun. On the end date the old repositories get a final port, a pointer README and issue transfers, and then they are archived.

Why

If you install SITMUN

  • Docker Compose compiles two Angular apps and three Java builds every time you deploy.
  • The README says secrets are required, but Compose starts with built-in development secrets (#113).
  • The PostgreSQL and Oracle Compose files in profiles/ do not build (#110).

If you want to contribute

  • You have to understand eight builds and a hidden step that publishes a shared library to your machine before the backend or proxy builds.
  • No repository has a contributing guide, a security policy, a code owners file, or a pull request template.
  • It is not written down that pull requests go to dev, while GitHub shows main as the default branch.
  • Git hooks in different components accept different commit messages.

If you maintain or release SITMUN

  • Four separate dependency catalogs drift apart. One build uses axion-release 1.18.0 and another uses 1.19.0.
  • Every release bumps 26 version strings in five components, although all five already move together at 1.2.9.
  • Tags clash. v1.0.0 exists in three repositories on different commits.
  • A change that touches two components needs two pull requests plus a pointer update here. Checks here cannot see files inside a component, so a backend database change does not run the database upgrade tests.

Target layout

sitmun/
  sitmun-backend          API (was sitmun-backend-core)
  sitmun-proxy            proxy (was sitmun-proxy-middleware)
  sitmun-admin            admin app (was sitmun-admin-app)
  sitmun-viewer           map viewer (was sitmun-viewer-app)
  sitmun-addons/
    sitmun-mbtiles          map tiles service, off unless a deployment enables it
  sitmun-common/          shared libraries (was sitmun-shared)
    sitmun-spring-upstream
    sitmun-proxy-contract
  sitmun-mobile/          outside the default build
    sitmun-edition          (was edition-mobile-app)
    sitmun-touristic        (was touristic-mobile-app)
  docker/  profiles/  e2e/  tools/  docs/  .github/
  compose.yaml  package.json  scripts/
  settings.gradle  build.gradle  gradle.properties  gradle/libs.versions.toml
  • Build. One root Gradle build. sitmun-backend, sitmun-proxy, sitmun-mbtiles, sitmun-spring-upstream and sitmun-proxy-contract are subprojects with one version catalog. Root Gradle tasks wrap the sitmun-admin and sitmun-viewer npm builds. sitmun-edition and sitmun-touristic stay outside the default build.
  • Removed. sitmun-catalog has no consumers and is deleted (sitmun-shared#5). The separate sitmun-shared build disappears into sitmun-common/. So do every mavenLocal() and publishToMavenLocal, the three axion-release blocks and the three setupGitHooks tasks.
  • Versions. One platform version in gradle.properties, tagged vX.Y.Z. The mobile apps tag sitmun-edition/X.Y.Z and sitmun-touristic/X.Y.Z, because app stores set their pace.
  • Images. A release publishes ghcr.io/sitmun/backend, proxy, mbtiles and front. compose.yaml pulls them for the chosen SITMUN_VERSION, with a build: fallback (#107).
  • Developer entry point. npm run dev starts the backend on H2 plus the admin, viewer and proxy dev servers with generated secrets. It is a Node script, so it runs on Windows, macOS and Linux. The Gradle toolchain pins Java. .nvmrc and engines pin Node.
  • Contributor files. CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CODEOWNERS, a pull request template, issue forms with a component field, a path labeler and DCO sign-off.
  • CI. One always-run ci-gate job is the only required check. It detects changed paths and waits on the matching workflows. CodeQL runs as one matrix with a category per component and language. Each E2E suite runs when its paths change, and all suites run on pushes to dev and main. The Liquibase immutability gate covers the backend changelog for real once it is in the tree.

What stays separate on purpose

Idea Verdict Reason
Merge the proxy into the backend No The proxy fetches arbitrary upstream URLs and runs JDBC against outside databases. The backend holds the JWT signing secret and database credentials. Separate processes are the security boundary.
Merge MBTiles into the backend No It is unauthenticated inside the network, has its own 1G memory limit and runs Spring Batch on H2.
One Angular workspace for admin and viewer Not now They already ship in one image. One lockfile would tie admin library upgrades to the viewer's api-sitna.
Devcontainer or a required version manager No Gradle and npm already run on every OS with pinned versions. Liquibase, Spring Framework and DataHub do not ship a devcontainer either.
Split mobile into its own repository No Root E2E (npm run e2e:mobile:web) covers both apps. A split loses that coverage.
Keep per-module tags No Practice is already lockstep. Per-module tags only create collisions.

A new repository, not a rename

A rename would keep GitHub redirects. A new repository does not.

  • Stars, watchers, forks and old clone URLs stay with the old repositories. Today they hold 10 stars and 26 forks in total.
  • Old clone URLs and .gitmodules entries keep working, because the old repositories are archived and never deleted.
  • Maven coordinates (org.sitmun:*) do not change. Image names are new.
  • Issue transfers keep redirects for issue URLs. Pull requests, releases and commit pages stay readable in the archived repositories.

Day 0. Snapshot import

  • Create sitmun/sitmun empty, with no README, license or .gitignore init.
  • Import each old repository with git filter-repo --to-subdirectory-filter onto its final sitmun-* directory. The final directory names exist from the first commit.
  • sitmun-application-stack is imported at the root, not into a subdirectory. Its e2e/, tools/, profiles/, scripts/, docker-compose.yml and .github/ become the new root. The import commit removes its gitlinks and .gitmodules.
  • The same rewrite changes #N in commit messages to sitmun/<oldrepo>#N. That link redirects to the issue once it is transferred.
  • Every old tag is imported as legacy/<repo>/<tag>, including the stack's own tags. Partner tags such as 1.2.7-DIPTA and the clashing v1.0.0 tags get new names this way. WIP tags are dropped. The run fails on any tag the map does not cover.
  • The new repository starts with no tags.
  • Only main and dev are imported. Feature branches stay in the old repositories.
  • Record the imported old SHA of main and dev per repository in tools/monorepo/ported.json. That is the starting point for ports.

Verification. For every directory, git rev-parse dev:<dir> must equal git rev-parse <snapshot>^{tree} of the old dev. A rewrite into a subdirectory keeps tree hashes, so this is one hash comparison per directory. Nobody else gets write access until all nine pass.

The import is reproducible. git filter-repo is deterministic for unchanged input. Confirmed by experiment with filter-repo a40bce548d2c and git 2.56.0. Two independent runs over sitmun-proxy-middleware (143 commits, 13 refs) gave identical SHAs. A reviewer can re-run the pinned command from tools/monorepo/ and compare. Commit signatures are dropped by the rewrite.

Day 0 result. Repository: sitmun/sitmun. 63 legacy/ tags. Stack tags are legacy/sitmun-application-stack/sitmun-application-stack/X.Y.Z. ported.json matches the old main and dev tips. Both rulesets are active. ci-gate is green on dev and red on main (Spotless in sitmun-common and sitmun-backend, plus one MBTiles integration test). 30 Dependabot pull requests were open.

Weeks 1 to 4. Refactor plus ports

The refactor does not wait. It starts in sitmun/sitmun on day 1 and edits module files directly. There is no merge-based sync, because the refactored directories no longer match the old trees.

Port mechanism

Ports use patches from the old repository, applied into the module's directory.

git -C <old> format-patch -o <out> <last-ported>..dev
# add the trailer and rewrite #N to sitmun/<repo>#N in each patch
git am -3 --directory=<sitmun-dir> <out>/*.patch

Tested on a toy module after a simulated restructure. The restructure deleted settings.gradle, rewrote build.gradle and moved src/*.java to src/main/java/. Results, all confirmed.

  • Source-only changes apply cleanly. An edit to a moved file follows the move through the 3-way fallback.
  • New files land at the old path. A file added under src/ in the old repository arrives at <sitmun-dir>/src/, not at the moved location. The porter moves it by hand.
  • Build-file changes conflict. An edit to a rewritten build.gradle stops with a content conflict. An edit to a deleted settings.gradle stops with a modify/delete conflict. The source hunks of the same commit are already staged. The porter resolves the build files, runs git am --continue, and the trailer stays.
  • Build files can be skipped. git am -3 --directory=<sitmun-dir> --exclude=<sitmun-dir>/build.gradle applies the rest of the commit. The path in --exclude must include the directory prefix. The bare file name does not match.
  • Merge commits are skipped. format-patch exports the commits without merges. A conflict resolution made inside a merge in the old repository is ported by hand.

Fallback. When a patch does not apply, port the old PR by hand as one commit in sitmun/sitmun. Use the old PR as the reference and carry one trailer per old commit it contains.

Tracking

  • Every port commit carries Ported-from: sitmun/<repo>@<old-sha>.
  • tools/monorepo/ported.json records the last ported old SHA of main and dev per repository.
  • The marker lives in a tracked file, not in a git ref. It changes in the same PR as the ports it covers, so review sees both. It survives every clone and falls under the branch rules. A ref would be invisible in PRs and is not fetched by default.

Proposed script tools/monorepo/unported.sh <repo> [branch].

  • Fetch the old repository's branch.
  • List git rev-list --no-merges --reverse <marker>..<branch>.
  • Drop every SHA that already appears in a Ported-from trailer on sitmun/sitmun dev. This covers ports done out of order.
  • Print what is left and exit non-zero if anything is.

A scheduled workflow runs it for all nine repositories and posts the counts. The end-date exit check is zero unported commits on main and dev of every old repository. Admin main holds 3 commits that are not on dev today, so main is checked on its own.

Keeping the port load small

  • Old-repository contributors keep build-file changes out of feature PRs. Build changes go to sitmun/sitmun.
  • In-flight PRs land or rebase early in the month, not on the last days.
  • Dependabot stays off in all nine old repositories for the month. Its PRs would only create port work.
  • Close the 17 open Dependabot PRs on sitmun-admin-app. The root Dependabot config in sitmun/sitmun redoes them against the new layout.
  • New features freeze in the old repositories one week before the end date. After that only fixes for in-flight work land there.
  • Work that is not already in flight goes to sitmun/sitmun from Day 0.
  • Each old repository gets a pinned issue on Day 0 that states the end date, the freeze date and where new work goes.

Refactor order

  1. Layout. Moves are git-tracked, so --directory and rename detection handle ports into them. The sitmun-* directories already exist from the import. Move the Dockerfiles to docker/, rename docker-compose.yml to compose.yaml and clean profiles/. Avoid moving sources inside a module during the month, because new files ported into it land at the old path.
  2. Root Gradle build and one catalog. Start with the modules that have the least in-flight work. That is sitmun-common/, sitmun-mbtiles and sitmun-proxy. sitmun-backend goes last.
  3. Version. Remove axion, setupGitHooks, mavenLocal and publishToMavenLocal, and move to one version in gradle.properties. Backend goes last here too.

In-flight work found on 27 Oct 2026, over the last 30 days.

Repository Commits on dev Open PRs (not Dependabot) Recent unmerged branches Touch build files
sitmun-backend-core 83 #355 29 #355, fix/292-style-name-unique, fix/294-literal-unique
sitmun-application-stack 56 #116, #118 1 #118 (profile Compose)
sitmun-admin-app 46 #485 0 none
sitmun-viewer-app 38 #204 1 none
sitmun-proxy-middleware 21 none 0 none
sitmun-mbtiles 11 none 0 none
sitmun-shared 6 none 1 none
touristic-mobile-app 6 #16 1 #16 (package.json)
edition-mobile-app 3 #26 1 #26 (package.json)

The backend carries most of the in-flight work. If its port load is high, its build-file refactor moves to the last week or to right after the end date.

Merged since that count: stack #122 and #133, shared #6. Still open against main: stack #116 and #118.

The stack itself

sitmun-application-stack is still active, with 56 commits on dev in 30 days and three open PRs. Its files become the root of sitmun/sitmun, and the root is refactored first. So the stack stops taking changes on Day 0. Merge #116, #118 and #122 before creation. Anything that misses Day 0 is ported like any other change, without --directory.

Issues and PRs

  • New issues and PRs for in-flight work go to the old repository until its end date. Everything else goes to sitmun/sitmun from Day 0.
  • Issues transfer per repository at the end date, open and closed. Archived repositories cannot transfer issues out, so every transfer finishes before archiving.
  • The stack is the exception. All its issues, including this one and #106 to #115, move on Day 0, because its root files move then.
  • Transfer one issue at a time with gh issue transfer, oldest first, paced under GitHub's secondary rate limits. Record old URL, new number and component in a CSV.
  • Every participant gets a transfer notification. Announce this with the end date.
  • Check org Projects before transfers. Transferred issues may need to be re-added to boards.
  • PRs cannot be transferred. Every old PR is merged and ported, or closed, before the end date.

Issue counts on 27 Oct 2026.

Repository Open Closed Open PRs
sitmun-application-stack 32 33 3
sitmun-backend-core 0 235 1
sitmun-admin-app 13 311 18
sitmun-viewer-app 22 124 1
sitmun-proxy-middleware 0 7 0
sitmun-mbtiles 0 0 0
sitmun-shared 3 0 1
edition-mobile-app 15 3 1
touristic-mobile-app 5 2 1
Total 90 715 26

805 issues transfer in total.

Labels must exist in sitmun/sitmun before any transfer, because a transfer keeps only labels that already exist by name. Create them on Day 0. The issues use bug, enhancement, documentation, duplicate, invalid, wontfix, question, help wanted, good first issue, planned, confirmed, someday, unconfirmed, stale, build, maintenance, verified, meta and dependencies. Add component:backend, component:proxy, component:admin, component:viewer, component:mbtiles, component:common, component:edition and component:touristic. Stack-wide issues stay without a component label.

Releases

  • During the month the old repositories may cut patch releases with their existing tags.
  • sitmun/sitmun cuts no release until the end date.
  • Every old-repository release made during the month is ported before the first vX.Y.Z. Its tag stays in the archived repository.
  • The first platform release is sitmun/sitmun's own vX.Y.Z, cut after the final port. It is the next platform version after 1.2.9. The number is a decision to take.
  • The 1.3.0 database baseline (#123) changes backend files. It is built in sitmun/sitmun, not in sitmun-backend-core.

Settings that do not carry over

A new repository starts with none of the old settings. This list covers only what exists today, checked with gh on 27 Oct 2026.

  • Team access. silme has push on the stack, admin, viewer, proxy and both mobile apps. nexus has push on the stack and admin and pull on backend, viewer and proxy. guadaltel has push on backend and proxy. atol-cd has push on backend and viewer. avansig has push on viewer. rbejar is a direct admin collaborator on backend. In one repository, write access spans every directory, so CODEOWNERS takes over the per-component boundary.
  • Actions secrets. No repository-level secrets exist. Org secrets HEROKU_EMAIL, HEROKU_TOKEN, SONAR_TOKEN, TOKEN_FOR_WORKFLOWS and USERNAME_FOR_WORKFLOWS are visible to sitmun/sitmun.
  • SonarCloud. main still runs SonarCloud in backend, admin and proxy. dev has dropped it. Decide whether to rebind it to sitmun/sitmun or drop it.
  • CodeQL. Six Analyze checks are green on sitmun/sitmun dev. Alert history stays in the old repositories.
  • Dependabot. Only admin has a config. Add a root .github/dependabot.yml in sitmun/sitmun with one entry per module directory.
  • Environments. heroku in backend. github-pages in viewer.
  • Pages. The viewer publishes https://sitmun.github.io/sitmun-viewer-app/ from a branch. Decide whether to keep it there or rebuild it from sitmun/sitmun under a new URL.
  • Rules. No branch protection exists anywhere. Backend has one ruleset, Sample, which is disabled. Create the dev and main rulesets in sitmun/sitmun with ci-gate required and code owner review.
  • GHCR. Images are new under ghcr.io/sitmun/. Link each package to sitmun/sitmun and grant it Actions write access when the release workflow lands.
  • Repository features. Wiki and Discussions are off on sitmun/sitmun.

Repository secrets, Dependabot secrets, Actions variables, webhooks, deploy keys and autolinks are absent in all nine repositories. Nothing to recreate there.

Risks

  • Port backlog at the end. Unported commits pile up in the last week. The scheduled unported.sh counts, the feature freeze and early landing of in-flight PRs keep it small.
  • Build-file conflict with an in-flight branch. A branch that edits build.gradle conflicts with the refactor. Three backend branches and #355 do today. They are ported by hand, or the backend build refactor waits until they land.
  • A contributor misses the announcement. The pinned issue in each old repository and the feature freeze date limit the damage. Late work is ported by hand.
  • Force-push in an old repository. It rewrites the commits the marker points at, and unported.sh loses its base. Rulesets block force-push and deletion on main and dev in all nine repositories before creation. None has branch protection today.
  • CI runs in both repositories for a month. A change runs CI in the old repository and again when it is ported. This is accepted. Scheduled workflows in the old repositories can be turned off to limit it.
  • Downstream pins. Consumers pin clone URLs, submodule URLs and image names. Clone URLs keep working against archived repositories. Maven coordinates do not change. Image names are new, so deployers switch once.

Steps

The end date is a decision. Suggested Day 0 plus 30 days, for example Friday 27 Nov 2026, with the feature freeze one week earlier on Friday 20 Nov 2026.

Before creation (one or two days)

  • Add rulesets blocking force-push and deletion on main and dev in all nine repositories.
  • Add the EUPL-1.2 LICENSE to sitmun-shared (sitmun-shared#4).
  • Delete sitmun-catalog (sitmun-shared#5, PR sitmun-shared#6).
  • Fix the profile Compose files (#110, PR #130). PR #118 remains open.
  • Exclude node_modules from the root Docker context (#114, PR #131). PR #116 remains open.
  • Merge or close the stack's #122.
  • Turn off Dependabot in all nine repositories and close the 17 admin Dependabot PRs.
  • Pin git-filter-repo, git, the tag map and the message rewrite under tools/monorepo/ (#132).
  • Announce Day 0, the feature freeze date and the end date. Tell partners their tags are renamed under legacy/.

Day 0

  • Create sitmun/sitmun empty.
  • Import the nine histories onto their final paths and push to dev.
  • Pass the tree check for every directory, then open write access.
  • Write tools/monorepo/ported.json with the imported SHAs.
  • Fix the root .gitignore (it ignores *.properties and *.jar, which would drop every Gradle wrapper).
  • Add root workflows, ci-gate, CODEOWNERS, the rulesets, the labels and the root Dependabot config.
  • Make sitmun-application-stack read-only and transfer its issues.
  • Pin the announcement issue in the other eight repositories.

Weeks 1 to 4

  • Layout refactor.
    • 1. Root E2E paths. sitmun#43
    • 2. Viewer E2E Node (#115). sitmun#44
    • 3. Liquibase paths and legacy tags. sitmun#45
    • 4. Dependabot batch. Superseded bumps are closed. Green minor and patch bumps stay open for review. Major bumps stay open.
    • 5. Docker layout: docker/ and compose.yaml. sitmun#48
    • 6. ci-gate builds images and runs E2E (#108). sitmun#49
    • 7. Setup scripts. sitmun#46
    • 8. Nested component GitHub files and root issue forms. sitmun#47
    • 9. Contributor docs (#111, #112). sitmun#50
    • 10. Contributor files on main. sitmun#51
  • Root Gradle build and one catalog, backend last.
  • One version, backend last.
  • Port in-flight work at least weekly with Ported-from trailers.
  • Feature freeze in the old repositories one week before the end date.

End date

  • Final port. unported.sh reports zero on main and dev for every old repository.
  • Merge and port, or close, every remaining old PR.
  • Make each old repository read-only.
  • Commit a pointer README to sitmun/sitmun and the matching directory on each old default branch.
  • Transfer each repository's issues and add component:<name>.
  • Archive all nine repositories.
  • Cut the first vX.Y.Z and publish the images.

How we know it worked

  • No gitlinks and no .gitmodules remain in sitmun/sitmun.
  • At import, each directory's tree hash equals the old dev root tree hash.
  • git log on a directory shows its old commits, and legacy/ tags check out.
  • Issue references in imported commit messages open the transferred issue.
  • unported.sh reports zero for every old repository on the end date.
  • Every old repository is archived with a pointer README, and its issues are in sitmun/sitmun with labels.
  • ./gradlew build at the root is the only Gradle build, and no settings.gradle remains below the root.
  • docker compose build succeeds, and the E2E suites pass in CI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    plannedActive backlog; exempt from stale bot

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions