From a7220ddb2bc95e817e53c71cb0422565dd3c8fbf Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 6 Jul 2026 10:55:58 -0500 Subject: [PATCH 1/4] =?UTF-8?q?fix(ci):=20robust=20prod=20deploy=20?= =?UTF-8?q?=E2=80=94=20drop=20dev-health=20gate,=20no=20tier-clobbering=20?= =?UTF-8?q?sed,=20rm=20-f=20before=20recreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prod deploy (deploy-mokogitea.yml, on push to main) failed the #733 release and silently corrupted the shared compose. Three fixes: - Removed the 'Verify dev environment is healthy' gate: it curled git.dev and exit 1 on failure, so a dev blip false-negatives the PROD deploy (wrong-tier gate; the dev->rc->main pipeline + RC env is the real gate). - Replaced 'sed s|mokogitea:...|:$TAG|' (matched ALL mokogitea service lines and clobbered the dev+rc ${MOKOGITEA_*_TAG} env-vars with the prod tag) with the env-var pattern: env $TAG_ENV=$TAG docker compose up -- drives only the target service, no sed. - Added 'docker rm -f $CONTAINER' + '-p gitea-dev' + '--force-recreate' before the compose up, fixing the 'Container name /mokogitea already in use' conflict. Aligns deploy-mokogitea.yml with the fixed deploy-dev/deploy-rc pattern. Host compose was separately restored to env-var form (the bad run had clobbered dev+rc tags). Long-term: cut over Template-Go#5 deploy-prod.yml. Refs #733, #752. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT --- .../workflows/custom/deploy-mokogitea.yml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.mokogitea/workflows/custom/deploy-mokogitea.yml b/.mokogitea/workflows/custom/deploy-mokogitea.yml index 43a36f598d..0af8f78ca3 100644 --- a/.mokogitea/workflows/custom/deploy-mokogitea.yml +++ b/.mokogitea/workflows/custom/deploy-mokogitea.yml @@ -39,17 +39,10 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - name: Verify dev environment is healthy - run: | - echo "Checking git.dev.mokoconsulting.tech health..." - if curl -sf --max-time 10 https://git.dev.mokoconsulting.tech/api/healthz; then - echo " Dev environment is healthy — proceeding with production deploy" - else - echo "::error::Dev environment is NOT healthy — blocking production deploy" - echo "Deploy to dev first (push to dev branch) and verify it passes before merging to main." - exit 1 - fi - + # NOTE: removed a "Verify dev environment is healthy" gate that curled + # git.dev and `exit 1` on failure — gating a PROD deploy on a live DEV + # health check is a cross-tier false-negative (a transient dev blip blocked + # prod). Pre-prod validation is the dev -> rc -> main pipeline + RC env. - name: Checkout source (for version detection) uses: actions/checkout@v4 with: @@ -66,17 +59,19 @@ jobs: ENV="${{ github.event.inputs.environment }}" fi if [ "$ENV" = "production" ]; then - echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT + echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT echo "container=mokogitea" >> $GITHUB_OUTPUT echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT echo "branch=main" >> $GITHUB_OUTPUT echo "tag=$VERSION" >> $GITHUB_OUTPUT + echo "tag_env=MOKOGITEA_TAG" >> $GITHUB_OUTPUT else echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT echo "container=mokogitea-dev" >> $GITHUB_OUTPUT echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT echo "branch=dev" >> $GITHUB_OUTPUT echo "tag=$VERSION-dev" >> $GITHUB_OUTPUT + echo "tag_env=MOKOGITEA_DEV_TAG" >> $GITHUB_OUTPUT fi - name: Write deploy key @@ -95,6 +90,7 @@ jobs: SOURCE_DIR: ${{ steps.config.outputs.source_dir }} COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }} CONTAINER: ${{ steps.config.outputs.container }} + TAG_ENV: ${{ steps.config.outputs.tag_env }} run: | HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}' IMAGE_FMT='Image: ${{ '{{' }}.Config.Image${{ '}}' }}' @@ -134,8 +130,12 @@ jobs: echo 'Restarting container...' cd $COMPOSE_DIR - sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:$TAG|' docker-compose.yml - docker compose up -d $CONTAINER + # Drive ONLY the target service's tag via its compose env-var (no sed — + # a blanket 'sed s|mokogitea:...|' clobbered the dev + rc service tags too). + # Remove any lingering fixed-name container first (avoids the "name already + # in use" conflict), then force-recreate under the shared compose project. + docker rm -f $CONTAINER 2>/dev/null || true + env $TAG_ENV="$TAG" docker compose -p gitea-dev up -d --force-recreate $CONTAINER echo 'Health check...' for i in 1 2 3 4 5 6 7 8; do -- 2.52.0 From 010d3dcbcf98b043d049bcd85eb42d6e81265e10 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Mon, 6 Jul 2026 17:04:06 +0000 Subject: [PATCH 2/4] chore: sync pre-release.yml from Template-Generic [skip ci] --- .mokogitea/workflows/pre-release.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index ff376b3e23..b212772dda 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -7,7 +7,7 @@ # INGROUP: mokocli.Release # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # PATH: /.mokogitea/workflows/pre-release.yml -# VERSION: 05.02.00 +# VERSION: 05.02.01 # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches name: "Universal: Pre-Release" @@ -162,7 +162,13 @@ jobs: git add -A git diff --cached --quiet || { git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" - git push origin HEAD 2>&1 + # Push the bump commit, but do NOT fail the release if the target branch + # is protected and the release identity is not on the push allowlist. + # The build proceeds from the in-tree bumped version regardless; if the + # push is rejected, the next run simply re-bumps from the same base. + if ! git push origin HEAD 2>&1; then + echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway." + fi } # Auto-detect element via manifest_element.php @@ -274,4 +280,4 @@ jobs: echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY - echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY + echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file -- 2.52.0 From 7f436128fd626c6e92a93004906111e89e4687fd Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 12 Jul 2026 18:35:31 -0500 Subject: [PATCH 3/4] fix(metadata): derive org from org profile instead of storing it The repo metadata org field was a stored, editable value that snapshotted the owner name and could drift when an organization was renamed. It is now derived from the org profile (repository owner) on read and is read-only. - models/repo/repo.go: add Repository.DerivedOrgName (owner FullName, falling back to the handle, then the denormalized OwnerName) - models/repo/repo_manifest.go: drop the Org struct field - models/migrations/v1_27/v369.go + migrations.go: migration #368 drops the repo_manifest.org column - routers/api/v1/repo/manifest.go: derive org in the response, ignore it on write - routers/web/repo/setting/metadata.go + templates: show org read-only (derived) - services/updateserver/joomla.go: use DerivedOrgName for the feed maintainer Closes #771 --- CHANGELOG.md | 1 + models/migrations/migrations.go | 1 + models/migrations/v1_27/v369.go | 19 +++++++++++++++++++ models/repo/repo.go | 17 +++++++++++++++++ models/repo/repo_manifest.go | 4 +++- routers/api/v1/repo/manifest.go | 12 ++++++------ routers/web/repo/setting/metadata.go | 6 ++++-- services/updateserver/joomla.go | 7 ++----- templates/repo/settings/metadata.tmpl | 3 ++- 9 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 models/migrations/v1_27/v369.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb77732ab..7da8cab7c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ - Cherry-pick upstream v1.26.4: walk git log context error handling — regression fix (#38185) ### Fixed +- Repo metadata `org` is now **derived from the org profile** (the repository owner) instead of being stored/editable, so it can never drift when an organization is renamed. The `org` column is dropped from `repo_manifest` (migration #368) and the field is derived on read via `Repository.DerivedOrgName` (owner display name, falling back to the handle) across the metadata API, the Settings → Metadata page (now read-only), and the Joomla update-server feed. The API `PUT` and MCP `metadata_update` now ignore `org` (read-only, like the already-derived `display_name`) (#771) - Fork server binary now compiles: `routers/api/v1/api.go` called `organization.HasOrgOrUserVisible`, which had been renamed to `IsOwnerVisibleToDoer`; the one missed call site broke `go build` of the entire `routers/api/v1` package (CI's Lint & Validate does not run a full build, so it went unnoticed) (#735) - Dev deploy workflow: the build/deploy step referenced runner-side values as `\$TAG` / `\$REGISTRY_TOKEN` inside an unquoted SSH heredoc, deferring expansion to the remote shell where those names are unset — the Docker tag collapsed to an empty `mokogitea:` and every dev deploy failed with `invalid reference format`. Runner values are now injected via an ssh env-prefix and the heredoc is quoted so each `$var` expands in exactly one place (#737) - Repaired unit-test compile and `go vet` failures: `CryptoRandomInt/String/Bytes` now return two values (updated `modules/util/util_test.go`), removed a redundant `&&` condition in `issue_comment.go`, and cleaned up isolated integration-test compile errors (#736) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 24ff05cc46..5a0eada68b 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -445,6 +445,7 @@ func prepareMigrationTasks() []*migration { newMigration(365, "Add org repo defaults table", v1_27.AddOrgRepoDefaultsTable), newMigration(366, "Add org email domain policy table", v1_27.AddOrgEmailDomainPolicyTable), newMigration(367, "Add user allowlists to org protected branch", v1_27.AddUserAllowlistsToOrgProtectedBranch), + newMigration(368, "Drop org from repo manifest (derived from org profile)", v1_27.DropOrgFromRepoManifest), } return preparedMigrations } diff --git a/models/migrations/v1_27/v369.go b/models/migrations/v1_27/v369.go new file mode 100644 index 0000000000..54585668f0 --- /dev/null +++ b/models/migrations/v1_27/v369.go @@ -0,0 +1,19 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package v1_27 + +import ( + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/migrations/base" + + "xorm.io/xorm" +) + +// DropOrgFromRepoManifest removes the org column from repo_manifest. The +// organization is now derived from the org profile (repository owner) on read +// rather than stored, so it never drifts on rename. See issue #771. +func DropOrgFromRepoManifest(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + return base.DropTableColumns(sess, "repo_manifest", "org") +} diff --git a/models/repo/repo.go b/models/repo/repo.go index ab7523e54e..cf99169a49 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -486,6 +486,23 @@ func (repo *Repository) LoadOwner(ctx context.Context) (err error) { return err } +// DerivedOrgName returns the organization name derived from the org profile +// (the repository owner). It prefers the owner's display name (FullName) and +// falls back to the owner handle (Name / OwnerName). The organization is always +// derived from the profile rather than stored, so it never drifts on rename. +func (repo *Repository) DerivedOrgName(ctx context.Context) string { + if err := repo.LoadOwner(ctx); err != nil || repo.Owner == nil { + return repo.OwnerName + } + if repo.Owner.FullName != "" { + return repo.Owner.FullName + } + if repo.Owner.Name != "" { + return repo.Owner.Name + } + return repo.OwnerName +} + // MustOwner always returns a valid *user_model.User object to avoid // conceptually impossible error handling. // It creates a fake object that contains error details diff --git a/models/repo/repo_manifest.go b/models/repo/repo_manifest.go index 47f13cfc78..8eb7f947b3 100644 --- a/models/repo/repo_manifest.go +++ b/models/repo/repo_manifest.go @@ -23,8 +23,10 @@ type RepoMetadata struct { RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"` // identity section + // NOTE: organization is intentionally NOT stored here. It is derived from + // the org profile (repository owner) on read via Repository.DerivedOrgName, + // so it can never drift from the owner on rename. See issue #771. Name string `xorm:"TEXT 'name'"` // project name - Org string `xorm:"TEXT 'org'"` // organization name Description string `xorm:"TEXT 'description'"` // project description LicenseSPDX string `xorm:"VARCHAR(50) 'license_spdx'"` // SPDX identifier, e.g. "GPL-3.0-or-later" LicenseName string `xorm:"TEXT 'license_name'"` // human-readable license name diff --git a/routers/api/v1/repo/manifest.go b/routers/api/v1/repo/manifest.go index 642eb76d5b..017d697b9a 100644 --- a/routers/api/v1/repo/manifest.go +++ b/routers/api/v1/repo/manifest.go @@ -14,7 +14,7 @@ import ( // apiMetadata is the JSON representation of a repo manifest. type apiMetadata struct { Name string `json:"name"` - Org string `json:"org"` + Org string `json:"org"` // read-only, derived from the org profile (repo owner) Description string `json:"description"` LicenseSPDX string `json:"license_spdx"` LicenseName string `json:"license_name"` @@ -83,14 +83,14 @@ func GetRepoMetadata(ctx *context.APIContext) { // Return defaults from repo metadata. ctx.JSON(http.StatusOK, &apiMetadata{ Name: ctx.Repo.Repository.Name, - Org: ctx.Repo.Repository.OwnerName, + Org: ctx.Repo.Repository.DerivedOrgName(ctx), Description: ctx.Repo.Repository.Description, }) return } ctx.JSON(http.StatusOK, &apiMetadata{ Name: m.Name, - Org: m.Org, + Org: ctx.Repo.Repository.DerivedOrgName(ctx), Description: m.Description, LicenseSPDX: m.LicenseSPDX, @@ -157,7 +157,6 @@ func UpdateRepoMetadata(ctx *context.APIContext) { m = &repo_model.RepoMetadata{ RepoID: ctx.Repo.Repository.ID, Name: ctx.Repo.Repository.Name, - Org: ctx.Repo.Repository.OwnerName, Description: ctx.Repo.Repository.Description, } } @@ -171,7 +170,8 @@ func UpdateRepoMetadata(ctx *context.APIContext) { } } setStr("name", &m.Name) - setStr("org", &m.Org) + // org is intentionally ignored: it is derived from the org profile + // (repository owner) on read, not stored. See issue #771. setStr("description", &m.Description) setStr("license_spdx", &m.LicenseSPDX) setStr("license_name", &m.LicenseName) @@ -204,7 +204,7 @@ func UpdateRepoMetadata(ctx *context.APIContext) { ctx.JSON(http.StatusOK, &apiMetadata{ Name: m.Name, - Org: m.Org, + Org: ctx.Repo.Repository.DerivedOrgName(ctx), Description: m.Description, LicenseSPDX: m.LicenseSPDX, diff --git a/routers/web/repo/setting/metadata.go b/routers/web/repo/setting/metadata.go index 0fe147810c..6bbea3617a 100644 --- a/routers/web/repo/setting/metadata.go +++ b/routers/web/repo/setting/metadata.go @@ -35,11 +35,13 @@ func Metadata(ctx *context.Context) { manifest = &repo_model.RepoMetadata{ RepoID: repoID, Name: ctx.Repo.Repository.Name, - Org: ctx.Repo.Repository.OwnerName, Description: ctx.Repo.Repository.Description, } } ctx.Data["Manifest"] = manifest + // Organization is derived from the org profile (repo owner), not stored. + // It is displayed read-only. See issue #771. + ctx.Data["DerivedOrg"] = ctx.Repo.Repository.DerivedOrgName(ctx) // Load repo-scoped custom fields. fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo) @@ -102,7 +104,7 @@ func saveMetadata(ctx *context.Context) { manifest := &repo_model.RepoMetadata{ RepoID: ctx.Repo.Repository.ID, Name: ctx.FormString("name"), - Org: ctx.FormString("org"), + // org is not stored: it is derived from the org profile on read. See issue #771. Description: ctx.Repo.Repository.Description, LicenseSPDX: spdx, LicenseName: licenseName, diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go index 6fb24241bd..42c1bd0b09 100644 --- a/services/updateserver/joomla.go +++ b/services/updateserver/joomla.go @@ -269,11 +269,8 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require phpMinimum := meta.PHPMinimum feedDescription := meta.Description - // Maintainer and URL always come from the org profile. - maintainer := repo.Owner.FullName - if maintainer == "" { - maintainer = repo.Owner.Name - } + // Maintainer/organization and URL always come from the org profile. + maintainer := repo.DerivedOrgName(ctx) maintainerURL := repo.Owner.Website if maintainerURL == "" { maintainerURL = fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name) diff --git a/templates/repo/settings/metadata.tmpl b/templates/repo/settings/metadata.tmpl index 85c14c15c6..5d27acaf09 100644 --- a/templates/repo/settings/metadata.tmpl +++ b/templates/repo/settings/metadata.tmpl @@ -18,7 +18,8 @@
- + +

Derived from the organization profile (repository owner).

-- 2.52.0 From 793e4885e8f20d64f62876a43f65501754ebafbb Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 12 Jul 2026 19:37:51 -0500 Subject: [PATCH 4/4] ci: re-trigger checks after runner recovery No code change. The prior run was canceled at 0s because all ubuntu-latest runners were offline; this re-fires CI now that a runner is back. Ref #771. -- 2.52.0