Restricts which email domains an organization's members may have. When a policy
is configured, a user can only be added to the org (via any team) if their
primary email matches one of the allowed domain globs.
Enforced at the single membership choke point services/org.AddTeamMember, which
every add path (API, web, group-sync) funnels through — so one check covers them
all. On violation it returns a typed ErrEmailDomainNotAllowed; the API team-add
handler maps it to 422.
- models/git/org_email_domain.go: OrgEmailDomainPolicy model + EmailAllowed
(domain glob match) + OrgEmailDomainAllowed + typed error + CRUD. Migration 366.
- API: GET/PATCH/DELETE /orgs/{org}/email_domain_policy.
- Enforcement in services/org/team.go; 422 mapping in routers/api/v1/org/team.go.
An empty policy imposes no restriction. This is the one bounded piece of the
"access/security" tier; org 2FA-required and IP allowlists were deliberately NOT
built here — they are cross-cutting enforcement (auth gating / request
middleware) that needs a compiler + tests, not a blind stacked PR.
Stacked on #731/#730/#729/#728 for migration ordering (this = 366). Swagger
omitted.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block), imports (git_model added to the
api team handler, gci order), typed-error detection, migration contiguous (366).
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Adds 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_01Wsno14cxE49MstXFs9G5KT
Adds 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_01Wsno14cxE49MstXFs9G5KT
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_01Wsno14cxE49MstXFs9G5KT
Two related additions:
1. Branch deletion as an org-level ability. OrgProtectedBranch gained
CanDelete / EnableDeleteAllowlist / DeleteAllowlistTeamIDs (migration 362),
ToProtectedBranch maps them, and the API (create/edit/response DTOs +
handlers) exposes enable_delete / enable_delete_allowlist /
delete_allowlist_teams. The layering merge already combined delete fields, so
org delete-protection now enforces once ToProtectedBranch populates them.
2. The repo Branch Protection view now renders each inherited org rule as an
expandable detail (direct push, force-push, branch deletion, merge, required
approvals, status checks, protected files) with team names resolved, instead
of three headline badges. Still read-only.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Verified by hand: struct-field gofmt alignment, template block nesting balances,
every .Rule field exists on OrgProtectedBranch, and all locale keys referenced
in the template are defined.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Org-level branch protection was already consulted at the single enforcement
choke point `GetFirstMatchProtectedBranchRule`, but only as a FALLBACK: if any
repo-level rule matched the branch, the org rule was ignored entirely. That let
a repo define a looser rule for a pattern and effectively opt out of the org's
protection.
Make the choke point LAYER the two rules instead: when both an org rule and a
repo rule match a branch, return their most-restrictive (fail-closed)
combination, so the org rule is a mandatory floor a repo can only tighten.
- models/git/protected_branch_merge.go: mergeMostRestrictive + helpers. Allow
flags AND'd; gate/require/block flags OR'd; RequiredApprovals max'd; required
sets (status contexts, protected files) unioned; allow sets (whitelists,
unprotected files) intersected. A disabled allowlist means "everyone", so it
only constrains when enabled.
- models/git/protected_branch_list.go: GetFirstMatchProtectedBranchRule now
fetches both the repo rule and the org rule and merges when both match;
returns whichever exists when only one matches. Org lookup factored into
getFirstMatchOrgProtectedBranchRule.
Supersedes the materialization approach previously proposed for this issue —
the org fallback already existed, so only this one function needed to change.
Fail-closed by design: any merge edge errs toward MORE protection (over-restrict)
rather than less, so it cannot open a hole.
Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here —
relying on CI to validate build, formatting, and tests.
Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Add configurable per-user/team/deploy-key allowlist for deleting
protected branches. Previously, protected branches could never be
deleted via git push. Now admins can configure deletion permissions
with the same granularity as force-push allowlists.
- 6 new model fields: CanDelete, EnableDeleteAllowlist, DeleteAllowlistUserIDs/TeamIDs, DeleteAllowlistDeployKeys, DeleteAllowlistActionsUser
- CanUserDelete() method with admin-level default (higher than push)
- Migration v361 adds columns to protected_branch table
- Pre-receive hook checks delete allowlist instead of unconditional block
- CanDeleteBranch service uses CanUserDelete instead of IsBranchProtected
- API create/edit endpoints support delete allowlist fields
- Web UI settings page with radio buttons and user/team dropdowns
- 12 new locale strings for the delete allowlist UI
Claude-Session: https://claude.ai/code/session_011AAFzotGMf3ayvXhEmStCd
Full namespace migration: update the Go module path and all import
statements from git.mokoconsulting.tech to code.mokoconsulting.tech.
Also updates all URL references in templates, workflows, configs,
tests, and documentation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebrand the built-in actions bot user from upstream Gitea naming to
MokoGitea branding:
- Name: gitea-actions → mokogitea-actions
- FullName: Gitea Actions → MokoGitea Actions
- Email: teabot@gitea.io → mokogitea-actions[bot]@mokoconsulting.tech
Add backward-compatible name recognition so all three bot name variants
(mokogitea-actions, gitea-actions, github-actions) with optional [bot]
suffix resolve to the same system user.
Add WhitelistActionsUser, MergeWhitelistActionsUser, and
ForcePushAllowlistActionsUser toggles to branch protection rules,
allowing CI/CD workflows to push to protected branches when explicitly
enabled. Previously the actions bot (virtual user ID -2) could never be
added to whitelist because updateUserWhitelist() only validates real
database users.
Closes#233
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the Go module path from code.gitea.io/gitea to
git.mokoconsulting.tech/MokoConsulting/MokoGitea across the entire
codebase.
Scope:
- go.mod module declaration
- 2,235 Go source files (import paths)
- Dockerfile WORKDIR and COPY paths
- Swagger API templates
- golangci.yml linter config
External dependencies (code.gitea.io/gitea-vet, code.gitea.io/sdk/gitea,
gitea.com/gitea/act, etc.) are intentionally NOT renamed — they are
separate upstream modules.
Closes#132
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add organization-scoped branch protection rules that cascade to all
repos within the org. Repo-level rules take precedence; org rules
serve as the fallback when no repo rule matches a branch.
- New table: org_protected_branch (migration v332)
- OrgProtectedBranch model with full CRUD operations
- API endpoints: GET/POST/PATCH/DELETE /api/v1/orgs/{org}/branch_protections
- Inheritance via GetFirstMatchProtectedBranchRule() fallback
- InheritedFrom field added to BranchProtection API response
- Org rules use team-based whitelists (no per-user IDs at org level)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Treat Commit Status Warnings as errors
> The root problem is that the definition of "warning" are different
across systems.
>
> * Sometimes, "warning" is treated as "acceptable" (Gitea 1.25)
> * Sometimes, "warning" is mapped from "Result.UNSTABLE", which means
"there are test failures" and it is "failure" in Gitea
>
> **To avoid breaking existing users, the best choice is to revert the
behavior on Gitea side: treat "warning" as "error".**
https://github.com/go-gitea/gitea/issues/37042#issuecomment-4158231611
fixes https://github.com/go-gitea/gitea/issues/37042
---------
Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Use shared repo permission resolution for Actions task users in issue
label remove and clear paths, and add a regression test for deleting
issue labels with a Gitea Actions token.
This fixes issue label deletion when the request is authenticated with a
Gitea Actions token.
Fixes#37011
The bug was that the delete path re-resolved repository permissions
using the normal user permission helper, which does not handle Actions
task users. As a result, `DELETE
/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{id}` could return
`500` for Actions tokens even though label listing and label addition
worked.
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
This PR migrates the web Actions run/job routes from index-based
`runIndex` or `jobIndex` to database IDs.
**⚠️ BREAKING ⚠️**: Existing saved links/bookmarks that use the old
index-based URLs will no longer resolve after this change.
Improvements of this change:
- Previously, `jobIndex` depended on list order, making it hard to
locate a specific job. Using `jobID` provides stable addressing.
- Web routes now align with API, which already use IDs.
- Behavior is closer to GitHub, which exposes run/job IDs in URLs.
- Provides a cleaner base for future features without relying on list
order.
- #36388 this PR improves the support for reusable workflows. If a job
uses a reusable workflow, it may contain multiple child jobs, which
makes relying on job index to locate a job much more complicated
---------
Signed-off-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Description
This PR adds a branch_count field to the repository API response.
Currently, clients have to fetch all branches via /branches just to
determine the total number of branches. This addition brings Gitea
closer to parity with GitLab's API and improves efficiency for UI/CLI
clients that need this metric.
Linked Issue
Fixes#35351
Changes
API Structs: Added BranchCount field to Repository struct in
modules/structs/repo.go.
Database Logic: Implemented CountBranches in models/git/branch.go using
XORM for efficient counting.
Service Layer: Updated the ToRepo conversion logic in
services/convert/repository.go to populate the new field during API
serialisation.
Tests: Added a new unit test TestCountBranches in
models/git/branch_test.go to verify counts (including handling of
deleted branches).
Screenshots
<img width="196" height="121" alt="Screenshot 2026-02-24 at 21 41 07"
src="https://github.com/user-attachments/assets/cd023e92-f338-448b-9e49-0a5d54cc96c2"
/>
Testing
Manually verified the output using curl against a local Gitea instance.
Verified that adding a branch increments the count and deleting a branch
(soft-delete) decrements it.
Ran backend linting: make lint-backend (Passed).
Ran specific unit test: go test -v -tags "sqlite sqlite_unlock_notify"
./models/git -run TestCountBranches (Passed).
Co-authored-by: silverwind <me@silverwind.io>
When display or search branch's pushed time, we should use
`updated_unix` rather than `commit_time`.
Fix#36633
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
Fixes: https://github.com/go-gitea/gitea/issues/36152
Enable the `nilnil` linter while adding `//nolint` comments to existing
violations. This will ensure no new issues enter the code base while we
can fix existing issues gradually.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Fix#36448
Removed unnecessary parameters from the LFS GC process and switched to
an ORDER BY id ASC strategy with a last-ID cursor to avoid missing or
duplicating meta object IDs.
---------
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This adds a per-repository default PR base branch and wires it through
PR entry points. It updates compare links and recently pushed branch
prompts to respect the configured base branch, and prevents auto-merge
cleanup from deleting the configured base branch on same-repo PRs.
## Behavior changes
- New PR compare links on repo home/issue list and branch list honor the
configured default PR base branch.
- The "recently pushed new branches" prompt now compares against the
configured base branch.
- Auto-merge branch cleanup skips deleting the configured base branch
(same-repo PRs only).
---------
Signed-off-by: Louis <116039387+tototomate123@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Fix#35781, #27472
The PR will not correct the wrong numbers automatically.
There is a cron task `check_repo_stats` which will be run when Gitea
start or midnight. It will correct the numbers.
* use a single function to do Action Tokens Permission checks
* allows easier customization
* add basic tests
* lfs file locks should work now
---------
Signed-off-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This PR adds a quick approve button on PR page to allow reviewers to
approve all pending checks. Only users with write permission to the
Actions unit can approve.
---------
Signed-off-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit adds the "You recently pushed to branch X" alert also to PR
overview, as opposed to only the repository's home page.
GitHub also shows this alert on the PR list, as well as the home page.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Extract from #34531
## Move Commit status state to a standalone package
Move the state from `structs` to `commitstatus` package. It also
introduce `CommitStatusStates` so that the combine function could be
used from UI and API logic.
## Combined commit status Changed
This PR will follow Github's combined commit status. Before this PR,
every commit status could be a combined one.
According to
https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#get-the-combined-status-for-a-specific-reference
> Additionally, a combined state is returned. The state is one of:
> failure if any of the contexts report as error or failure
> pending if there are no statuses or a context is pending
> success if the latest status for all contexts is success
This PR will follow that rule and remove the `NoBetterThan` logic. This
also fixes the inconsistent between UI and API. In the API convert
package, it has implemented this which is different from the UI. It also
fixed the missing `URL` and `CommitURL` in the API.
## `CalcCommitStatus` return nil if there is no commit statuses
The behavior of `CalcCommitStatus` is changed. If the parameter commit
statuses is empty, it will return nil. The reference places should check
the returned value themselves.
Similar to #34544, this PR changes the `opts` argument in
`SearchRepositoryByName()` to be passed by value instead of by pointer,
as its mutations do not escape the function scope and are not used
elsewhere. This simplifies reasoning about the function and avoids
unnecessary pointer usage.
This insight emerged during an initial attempt to refactor
`RenderUserSearch()`, which currently intermixes multiple concerns.
---------
Co-authored-by: Philip Peterson <philip-peterson@users.noreply.github.com>
Fix#28144
To make the resources will be cleanup once failed. All repository
operations now follow a consistent pattern:
- 1. Create a database record for the repository with the status
being_migrated.
- 2. Register a deferred cleanup function to delete the repository and
its related data if the operation fails.
- 3. Perform the actual Git and database operations step by step.
- 4. Upon successful completion, update the repository’s status to
ready.
The adopt operation is a special case — if it fails, the repository on
disk should not be deleted.
The pull request list API is slow, for every pull request, it needs to
open a git repository. Assume it has 30 records, there will be 30 sub
processes back because every repository will open a git cat-file --batch
sub process. This PR use base git repository to get the head commit id
rather than read it from head repository to avoid open any head git
repository.