Merge pull request 'fix(metadata): derive org from org profile instead of storing it (#771)' (#772) from fix/metadata-org-derived-from-profile into main
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 31s
Generic: Project CI / Tests (pull_request) Successful in 31s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 47s
Deploy MokoGitea / deploy (push) Successful in 5m48s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled

This commit was merged in pull request #772.
This commit is contained in:
2026-07-13 00:40:52 +00:00
9 changed files with 55 additions and 15 deletions
+1
View File
@@ -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)
+1
View File
@@ -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
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// 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")
}
+17
View File
@@ -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
+3 -1
View File
@@ -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
+6 -6
View File
@@ -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,
+4 -2
View File
@@ -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,
+2 -5
View File
@@ -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)
+2 -1
View File
@@ -18,7 +18,8 @@
</div>
<div class="field">
<label>Organization</label>
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
<input value="{{.DerivedOrg}}" placeholder="Organization" readonly>
<p class="help">Derived from the organization profile (repository owner).</p>
</div>
</div>
<div class="three fields">