feat(org): org-level repository defaults applied on repo create/transfer (#727) #731
Reference in New Issue
Block a user
Delete Branch "feat/org-repo-defaults"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of the org-governance series (#728, #729, #730). Adds a single per-org repository-defaults config, applied to a repo when it's created in or transferred into the org.
What it does
Repository.IsPrivate, a plain field).ApplyPRDefaults) — allowed merge styles, default merge style, and auto-delete-branch-after-merge, written to the repo's pull-requests unit config viarepo_service.UpdateRepositoryUnits.Applied through a notifier (
services/org/notifier.go) onCreateRepository/TransferRepository— the same decoupling used elsewhere to avoid theservices/repository → services/orgcycle.Safety
Best-effort: the notifier logs and swallows every error, so a defaults bug can never break repository creation or transfer — worst case the defaults silently don't apply.
ForcePrivateis the one hard constraint (a repo field); the PR-unit write is skipped cleanly if the repo has no pull-requests unit.How
models/git/org_repo_defaults.go:OrgRepoDefaults(one row/org) + CRUD. Migration 365.GET/PATCH/DELETE /orgs/{org}/repo_defaults.routers/init.go(org_service.Init()).Caveats
gofmt'd/tested. Hand-verified: gofmt (tabs, no blank-in-block, DTO/struct-literal alignment), imports used, noInit()collision inservices/org, notifier method signatures match theNotifierinterface, migration contiguous. Integration check: create a repo in an org withforce_private+ PR defaults set and confirm the new repo is private with the configured merge styles.https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
LandingPageType.Mode defaults to "" (Go zero value), and the template renders the home radio as checked for an empty Mode. The initial radio fill would evaluate home.checked = ("home" === "") = false, unchecking the default on a fresh install. Skip assignment when the config value is empty so the server-rendered selection is preserved. Adds a test for the empty-value case.Adds org-level tag protection as a parallel to org-level branch protection. An org tag rule is {NamePattern, AllowlistTeamIDs}; it cascades to every repo in the org and layers on top of the repo's own protected tags — a tag is controllable (push/delete) only if allowed at BOTH levels (fail-closed). - models/git/org_protected_tag.go: OrgProtectedTag model + CRUD + ToProtectedTag() (reuses the ProtectedTag matcher/allowlist logic) + IsUserAllowedToControlTagInRepo() which ANDs the repo decision with the org decision. Migration 363. - API: /orgs/{org}/tag_protections CRUD (routers/api/v1/org/tag_protection.go, DTOs in modules/structs/org_tag.go, wired in api.go). - Enforcement: the git push/delete hook (hook_pre_receive.go) and the two release paths (release.go create/delete) now call the layered check, so no per-site tag logic changes beyond swapping the helper. - View: the repo Tag settings page lists inherited org tag rules read-only. Stacked on #728 (branch-protection PR) for migration ordering — merge #728 first. Swagger annotations omitted (can't regenerate the swagger JSON without the toolchain); routes still register. Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block, struct alignment), template nesting balances, all .Rule fields exist on OrgProtectedTag, all locale keys defined, JSON valid, migration contiguous (363). Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTAdds a single per-org push policy that cascades to every repo of the org and is enforced in the pre-receive hook: - Branch/tag name conventions (glob) — a pushed ref name must match. Fail-closed. - Mandatory secret-scanning block-on-push — org can force secret blocking that a repo cannot disable (overrides the per-repo scanner config in the orchestrator). - Max pushed-file size — rejects a tip tree containing a blob over the limit. - Blocked file-path patterns — rejects pushes changing matching paths (reuses pull_service.CheckFileProtection). The two content checks (blocked paths, max size) FAIL OPEN on any error so a policy/parsing bug can never wedge all pushes; naming is fail-closed. - models/git/org_push_policy.go: OrgPushPolicy model + CRUD + matchers + GetOrgPushPolicyForRepo. Migration 364. - API: GET/PATCH/DELETE /orgs/{org}/push_policy (routers/api/v1/org/push_policy.go, DTOs in modules/structs/org_push_policy.go, wired in api.go). - Enforcement: routers/private/hook_pre_receive.go (branch: naming + blocked paths + max size; tag: naming) and services/security/orchestrator.go (secret mandate). Deferred: a repo-facing read-only view of the org push policy (it is an org-wide config, not per-repo overlay rules; readable via the API for now). Stacked on #729/#728 for migration ordering (this = 364). Swagger annotations omitted (can't regenerate without the toolchain). Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block), escape sequences in the ls-tree parser, imports used, migration contiguous (364), fail-open on content checks. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTAdds a single per-org repository-defaults config, applied to a repo when it is created in or transferred into the org via a notifier (services/org): - ForcePrivate — force new/transferred repos private (Repository.IsPrivate). - PR defaults (when ApplyPRDefaults) — allowed merge styles, default merge style, and auto-delete-branch-after-merge, written to the repo's pull-requests unit config via repo_service.UpdateRepositoryUnits. Best-effort: the notifier logs and swallows errors, so a defaults bug can never break repository creation or transfer. - models/git/org_repo_defaults.go: OrgRepoDefaults model + CRUD + migration 365. - API: GET/PATCH/DELETE /orgs/{org}/repo_defaults. - services/org/notifier.go: CreateRepository/TransferRepository -> apply defaults; registered from routers/init.go (org_service.Init()). Stacked on #730/#729/#728 for migration ordering (this = 365). Swagger omitted. Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block, struct/DTO alignment), imports used, no Init() collision in services/org, migration contiguous (365), notifier signatures match the Notifier interface. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT58711210f1tod3134b1c53