Compare commits

..

1 Commits

Author SHA1 Message Date
jmiller 56e65268d7 feat: org branch protection user allowlists + actions-bot toggles (#734)
Org-level branch protection rules previously supported team-only
allowlists. This adds per-user allowlists (resolved by username OR
email) and "allow the actions bot" toggles for the push, merge and
force-push allowlists, plus a per-user approvals allowlist -- mirroring
the repo-level ProtectedBranch fields exactly. These carry through
ToProtectedBranch() into the standard enforcement path.

Model: add WhitelistUserIDs/MergeWhitelistUserIDs/
ForcePushAllowlistUserIDs/ApprovalsWhitelistUserIDs ([]int64 JSON) and
WhitelistActionsUser/MergeWhitelistActionsUser/
ForcePushAllowlistActionsUser (bool) to OrgProtectedBranch, and copy
them in ToProtectedBranch().

Migration 362 (v368-style file v363.go): additive columns via x.Sync.

API: new resolveUserIDs helper (username-then-email lookup, dedupe,
422 on unknown); Create/Edit set the new fields gated on the matching
Enable* flags; GET resolves stored user IDs back to usernames and
returns the actions-user bools.

DTOs: add *Usernames []string + *ActionsUser bool to the Create/Edit/
response structs, using the same json names as repo_branch.go.

Swagger: register OrgBranchProtection(/List) responses and the
Create/Edit option bodies; regenerate specs for the org endpoints.

Delete allowlists are intentionally omitted -- org branch protection
has no delete-allowlist concept (no CanDelete/EnableDeleteAllowlist/
DeleteAllowlistTeamIDs), so there is no team allowlist to extend.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 17:17:20 -05:00
33 changed files with 1778 additions and 250 deletions
+4 -5
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -34,8 +34,7 @@ jobs:
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request') &&
!startsWith(github.event.repository.name, 'Template-')
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
@@ -53,7 +52,7 @@ jobs:
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
+19 -11
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/auto-release.yml
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
@@ -142,8 +142,8 @@ jobs:
run: |
git fetch origin rc
git checkout rc
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
@@ -214,8 +214,8 @@ jobs:
- name: Configure git for bot pushes
run: |
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
@@ -426,7 +426,7 @@ jobs:
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
@@ -438,9 +438,17 @@ jobs:
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
# (cascade-dev.yml), which runs after this release completes.
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
+2 -2
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
+7 -103
View File
@@ -1,106 +1,10 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoStandards.Cascade
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/cascade-dev.yml
# VERSION: 02.00.00
# BRIEF: Cascade main -> dev via PR; auto-merge only if conflict-free, else notify
name: "Cascade Main -> Dev"
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
pull-requests: write
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
# ntfy destination is configured via repo or org variables (org vars are inherited).
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'gitea-releases' }}
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
jobs:
cascade:
name: Cascade main -> dev
noop:
runs-on: ubuntu-latest
steps:
- name: Open main -> dev PR (auto-merge if clean, else notify)
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -uo pipefail
API="${MOKOGITEA_URL}/api/v1/repos/${REPO}"
AUTH="Authorization: token ${TOKEN}"
jqnum() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
# 0. dev must exist
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
echo "No dev branch - nothing to cascade."; exit 0
fi
# 1. is main ahead of dev?
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
if [ "${AHEAD:-0}" -eq 0 ]; then
echo "dev already up to date with main."; exit 0
fi
echo "main is ${AHEAD} commit(s) ahead of dev."
# 2. reuse an open main->dev PR, else create one
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
if [ -z "$PR" ]; then
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges only if conflict-free; otherwise left open for manual resolution."}')
PR=$(printf '%s' "$RESP" | jqnum number)
if [ -z "$PR" ]; then
echo "::warning::Could not open cascade PR: $RESP"; exit 0
fi
echo "Opened cascade PR #${PR}"
else
echo "Reusing open cascade PR #${PR}"
fi
# 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE=""
for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable)
case "$MERGEABLE" in True|False) break ;; esac
sleep 3
done
echo "mergeable=${MERGEABLE}"
notify() {
curl -sS \
-H "Title: ${REPO}: dev cascade needs manual merge" \
-H "Tags: warning,twisted_rightwards_arrows" \
-H "Priority: high" \
-H "Click: ${MOKOGITEA_URL}/${REPO}/pulls/${PR}" \
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
}
# 4. auto-merge only if conflict-free; otherwise notify
if [ "$MERGEABLE" = "True" ]; then
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
else
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
notify "could not be auto-merged (HTTP ${CODE})."
fi
else
echo "::warning::Cascade PR #${PR} has conflicts (mergeable=${MERGEABLE}); sending notification."
notify "has conflicts and cannot be merged automatically."
fi
- run: echo "Cascade disabled — auto-release handles dev recreation"
+3 -3
View File
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails.
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.mokogitea/workflows/cleanup.yml
+3 -3
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /.mokogitea/workflows/gitleaks.yml
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
+1 -1
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.mokogitea/workflows/notify.yml
+5 -79
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pr-check.yml
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge
@@ -97,80 +97,6 @@ jobs:
echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Docs Update Gate (main PRs) ─────────────────────────────────────────
require-docs:
name: Require Docs Update
runs-on: ubuntu-latest
# Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated.
if: ${{ github.base_ref == 'main' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Require README.md and CHANGELOG.md in the PR diff
run: |
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)"
if [ -z "$CHANGED" ]; then
git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true
CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)"
fi
echo "Changed files in PR:"
echo "$CHANGED"
MISSING=""
echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md"
echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md"
if [ -n "$MISSING" ]; then
echo "::error::PRs into main must update: ${MISSING}"
{
echo "## Docs Update Required"
echo ""
echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**."
echo ""
echo "Not updated in this PR: **${MISSING}**"
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "Docs update present (README.md + CHANGELOG.md)"
echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY"
# ── Wiki Update Reminder (main PRs, non-blocking) ───────────────────────
wiki-reminder:
name: Wiki Update Reminder
runs-on: ubuntu-latest
if: ${{ github.base_ref == 'main' }}
steps:
- name: Remind to update the wiki
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
set -uo pipefail
{
echo "## Wiki Update Reminder"
echo ""
echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:"
echo ""
echo "- ${SERVER}/${REPO}/wiki"
echo ""
echo "_Non-blocking reminder._"
} >> "$GITHUB_STEP_SUMMARY"
# Post a single PR comment (idempotent via hidden marker); best-effort, never fails.
API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments"
if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then
existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)"
if [ "${existing:-0}" -eq 0 ]; then
curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \
-d '{"body":"<!-- wiki-reminder -->\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true
fi
fi
echo "Wiki reminder emitted (non-blocking)."
# ── Secret Scanning ──────────────────────────────────────────────────
gitleaks:
name: Secret Scan
@@ -350,7 +276,7 @@ jobs:
joomla)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla manifest found (MokoSuite site)"
echo "::warning::No Joomla manifest found (WaaS site)"
exit 0
fi
echo "Manifest: ${MANIFEST}"
@@ -363,7 +289,7 @@ jobs:
# Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
+6 -6
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pre-release.yml
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
@@ -84,7 +84,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
@@ -156,8 +156,8 @@ jobs:
fi
# Commit version bump
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
+3 -4
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -25,8 +25,7 @@ jobs:
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == false &&
startsWith(github.event.pull_request.head.ref, 'rc/') &&
!startsWith(github.event.repository.name, 'Template-')
startsWith(github.event.pull_request.head.ref, 'rc/')
steps:
- name: Rename branch
+4 -4
View File
@@ -6,10 +6,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/repo-health.yml
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================
@@ -88,7 +88,7 @@ jobs:
# Hardcoded authorized users — always allowed
case "$ACTOR" in
jmiller|mokogitea-actions[bot])
jmiller|gitea-actions[bot])
ALLOWED=true
PERMISSION=admin
METHOD="hardcoded allowlist"
+1 -2
View File
@@ -10,12 +10,11 @@ on:
jobs:
sync:
runs-on: ubuntu-latest
if: ${{ startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout mokocli
uses: actions/checkout@v4
with:
repository: MokoConsulting/MokoCLI
repository: MokoConsulting/mokocli
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Setup PHP
+1 -2
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow.Template
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
@@ -34,7 +34,6 @@ jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Validate version format
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
@@ -27,10 +27,9 @@ jobs:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')))
!contains(github.event.pull_request.title, '[skip sync]'))
steps:
- name: Determine platform from repo name
@@ -53,7 +52,7 @@ jobs:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
+18 -2
View File
@@ -28,16 +28,23 @@ type OrgProtectedBranch struct {
CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool `xorm:"NOT NULL DEFAULT false"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
StatusCheckContexts []string `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
@@ -81,8 +88,10 @@ func (o *OrgProtectedBranch) Match(branchName string) bool {
}
// ToProtectedBranch converts an org-level rule to a ProtectedBranch for use
// in the standard enforcement path. Fields that are user-scoped (WhitelistUserIDs etc.)
// are left empty because org rules only reference teams.
// in the standard enforcement path. Both team-scoped and user-scoped allowlists
// (plus the "allow the actions bot" toggles) are carried over so org rules can
// grant push/merge/force-push/approval access to individual users and the actions
// bot, in addition to teams.
func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch {
return &ProtectedBranch{
ID: o.ID,
@@ -91,16 +100,23 @@ func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch {
CanPush: o.CanPush,
EnableWhitelist: o.EnableWhitelist,
WhitelistTeamIDs: o.WhitelistTeamIDs,
WhitelistUserIDs: o.WhitelistUserIDs,
WhitelistActionsUser: o.WhitelistActionsUser,
EnableMergeWhitelist: o.EnableMergeWhitelist,
MergeWhitelistTeamIDs: o.MergeWhitelistTeamIDs,
MergeWhitelistUserIDs: o.MergeWhitelistUserIDs,
MergeWhitelistActionsUser: o.MergeWhitelistActionsUser,
CanForcePush: o.CanForcePush,
EnableForcePushAllowlist: o.EnableForcePushAllowlist,
ForcePushAllowlistTeamIDs: o.ForcePushAllowlistTeamIDs,
ForcePushAllowlistUserIDs: o.ForcePushAllowlistUserIDs,
ForcePushAllowlistActionsUser: o.ForcePushAllowlistActionsUser,
EnableStatusCheck: o.EnableStatusCheck,
StatusCheckContexts: o.StatusCheckContexts,
RequiredApprovals: o.RequiredApprovals,
EnableApprovalsWhitelist: o.EnableApprovalsWhitelist,
ApprovalsWhitelistTeamIDs: o.ApprovalsWhitelistTeamIDs,
ApprovalsWhitelistUserIDs: o.ApprovalsWhitelistUserIDs,
BlockOnRejectedReviews: o.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: o.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: o.BlockOnOutdatedBranch,
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestOrgProtectedBranchToProtectedBranch verifies that the org-level rule carries
// its team allowlists, user allowlists and actions-user toggles across to the
// standard ProtectedBranch used by the enforcement path.
func TestOrgProtectedBranchToProtectedBranch(t *testing.T) {
org := &OrgProtectedBranch{
ID: 7,
RuleName: "main",
Priority: 3,
CanPush: true,
EnableWhitelist: true,
WhitelistTeamIDs: []int64{1, 2},
WhitelistUserIDs: []int64{10, 11},
WhitelistActionsUser: true,
EnableMergeWhitelist: true,
MergeWhitelistTeamIDs: []int64{3},
MergeWhitelistUserIDs: []int64{12},
MergeWhitelistActionsUser: true,
CanForcePush: true,
EnableForcePushAllowlist: true,
ForcePushAllowlistTeamIDs: []int64{4},
ForcePushAllowlistUserIDs: []int64{13, 14},
ForcePushAllowlistActionsUser: true,
RequiredApprovals: 2,
EnableApprovalsWhitelist: true,
ApprovalsWhitelistTeamIDs: []int64{5},
ApprovalsWhitelistUserIDs: []int64{15},
}
pb := org.ToProtectedBranch()
// Push allowlist
assert.Equal(t, []int64{1, 2}, pb.WhitelistTeamIDs)
assert.Equal(t, []int64{10, 11}, pb.WhitelistUserIDs)
assert.True(t, pb.WhitelistActionsUser)
// Merge allowlist
assert.Equal(t, []int64{3}, pb.MergeWhitelistTeamIDs)
assert.Equal(t, []int64{12}, pb.MergeWhitelistUserIDs)
assert.True(t, pb.MergeWhitelistActionsUser)
// Force-push allowlist
assert.Equal(t, []int64{4}, pb.ForcePushAllowlistTeamIDs)
assert.Equal(t, []int64{13, 14}, pb.ForcePushAllowlistUserIDs)
assert.True(t, pb.ForcePushAllowlistActionsUser)
// Approvals allowlist (no actions-user at the approval stage, matching repo-level)
assert.Equal(t, []int64{5}, pb.ApprovalsWhitelistTeamIDs)
assert.Equal(t, []int64{15}, pb.ApprovalsWhitelistUserIDs)
// Sanity: identity fields propagate
assert.Equal(t, int64(7), pb.ID)
assert.Equal(t, "main", pb.RuleName)
assert.Equal(t, int64(3), pb.Priority)
assert.EqualValues(t, 2, pb.RequiredApprovals)
}
// TestOrgProtectedBranchToProtectedBranchEmpty verifies that when the org rule does
// not grant actions-user access, the toggles remain false (opt-in semantics).
func TestOrgProtectedBranchToProtectedBranchEmpty(t *testing.T) {
pb := (&OrgProtectedBranch{RuleName: "release/*"}).ToProtectedBranch()
assert.False(t, pb.WhitelistActionsUser)
assert.False(t, pb.MergeWhitelistActionsUser)
assert.False(t, pb.ForcePushAllowlistActionsUser)
assert.Nil(t, pb.WhitelistUserIDs)
assert.Nil(t, pb.MergeWhitelistUserIDs)
assert.Nil(t, pb.ForcePushAllowlistUserIDs)
assert.Nil(t, pb.ApprovalsWhitelistUserIDs)
}
+1
View File
@@ -439,6 +439,7 @@ func prepareMigrationTasks() []*migration {
newMigration(359, "Add deploy fields to repo manifest", v1_27.AddDeployFieldsToRepoManifest),
newMigration(360, "Add delete allowlist to protected branch", v1_27.AddDeleteAllowlistToProtectedBranch),
newMigration(361, "Add cascade merge rule table", v1_27.AddCascadeMergeRuleTable),
newMigration(362, "Add user allowlists and actions-user toggles to org protected branch", v1_27.AddUserAllowlistsToOrgProtectedBranch),
}
return preparedMigrations
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddUserAllowlistsToOrgProtectedBranch adds per-user allowlists (by user ID) and
// "allow the actions bot" toggles to org-level branch protection rules, alongside
// the existing team-only allowlists. Additive columns only.
func AddUserAllowlistsToOrgProtectedBranch(x *xorm.Engine) error {
type OrgProtectedBranch struct {
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
}
return x.Sync(new(OrgProtectedBranch))
}
+21
View File
@@ -14,16 +14,23 @@ type OrgBranchProtection struct {
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
@@ -46,16 +53,23 @@ type CreateOrgBranchProtectionOption struct {
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
@@ -73,16 +87,23 @@ type EditOrgBranchProtectionOption struct {
EnablePush *bool `json:"enable_push"`
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser *bool `json:"push_whitelist_actions_user"`
EnableForcePush *bool `json:"enable_force_push"`
EnableForcePushAllowlist *bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser *bool `json:"force_push_allowlist_actions_user"`
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser *bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck *bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals *int64 `json:"required_approvals"`
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
+125 -2
View File
@@ -8,6 +8,7 @@ import (
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
@@ -36,6 +37,18 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec
return names
}
resolveUserNames := func(ids []int64) []string {
names := make([]string, 0, len(ids))
for _, id := range ids {
u, err := user_model.GetUserByID(ctx, id)
if err != nil {
continue
}
names = append(names, u.Name)
}
return names
}
return &api.OrgBranchProtection{
ID: rule.ID,
OrgID: rule.OrgID,
@@ -44,16 +57,23 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec
EnablePush: rule.CanPush,
EnablePushWhitelist: rule.EnableWhitelist,
PushWhitelistTeams: resolveTeamNames(rule.WhitelistTeamIDs),
PushWhitelistUsernames: resolveUserNames(rule.WhitelistUserIDs),
PushWhitelistActionsUser: rule.WhitelistActionsUser,
EnableForcePush: rule.CanForcePush,
EnableForcePushAllowlist: rule.EnableForcePushAllowlist,
ForcePushAllowlistTeams: resolveTeamNames(rule.ForcePushAllowlistTeamIDs),
ForcePushAllowlistUsernames: resolveUserNames(rule.ForcePushAllowlistUserIDs),
ForcePushAllowlistActionsUser: rule.ForcePushAllowlistActionsUser,
EnableMergeWhitelist: rule.EnableMergeWhitelist,
MergeWhitelistTeams: resolveTeamNames(rule.MergeWhitelistTeamIDs),
MergeWhitelistUsernames: resolveUserNames(rule.MergeWhitelistUserIDs),
MergeWhitelistActionsUser: rule.MergeWhitelistActionsUser,
EnableStatusCheck: rule.EnableStatusCheck,
StatusCheckContexts: rule.StatusCheckContexts,
RequiredApprovals: rule.RequiredApprovals,
EnableApprovalsWhitelist: rule.EnableApprovalsWhitelist,
ApprovalsWhitelistTeams: resolveTeamNames(rule.ApprovalsWhitelistTeamIDs),
ApprovalsWhitelistUsernames: resolveUserNames(rule.ApprovalsWhitelistUserIDs),
BlockOnRejectedReviews: rule.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: rule.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: rule.BlockOnOutdatedBranch,
@@ -85,6 +105,45 @@ func resolveTeamIDs(ctx *context.APIContext, orgID int64, names []string) ([]int
return ids, true
}
// resolveUserIDs converts a list of usernames or emails to user IDs, returning 422
// on any entry that matches no user. Each entry is first looked up by username and,
// if that user does not exist, by email address. Empty entries are ignored and the
// resulting IDs are deduplicated.
func resolveUserIDs(ctx *context.APIContext, names []string) ([]int64, bool) {
if len(names) == 0 {
return nil, true
}
seen := make(map[int64]struct{}, len(names))
ids := make([]int64, 0, len(names))
for _, name := range names {
if name == "" {
continue
}
u, err := user_model.GetUserByName(ctx, name)
if err != nil {
if !user_model.IsErrUserNotExist(err) {
ctx.APIErrorInternal(err)
return nil, false
}
u, err = user_model.GetUserByEmail(ctx, name)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIError(http.StatusUnprocessableEntity, "user does not exist: "+name)
} else {
ctx.APIErrorInternal(err)
}
return nil, false
}
}
if _, ok := seen[u.ID]; ok {
continue
}
seen[u.ID] = struct{}{}
ids = append(ids, u.ID)
}
return ids, true
}
// ListOrgBranchProtections list org-level branch protection rules
func ListOrgBranchProtections(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/branch_protections organization orgListBranchProtections
@@ -212,23 +271,50 @@ func CreateOrgBranchProtection(ctx *context.APIContext) {
return
}
pushUsers, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames)
if !ok {
return
}
forcePushUsers, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames)
if !ok {
return
}
mergeUsers, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames)
if !ok {
return
}
approvalsUsers, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames)
if !ok {
return
}
enablePushWhitelist := form.EnablePush && form.EnablePushWhitelist
enableForcePushAllowlist := form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist
rule := &git_model.OrgProtectedBranch{
OrgID: orgID,
RuleName: form.RuleName,
Priority: form.Priority,
CanPush: form.EnablePush,
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
EnableWhitelist: enablePushWhitelist,
WhitelistTeamIDs: pushTeams,
WhitelistUserIDs: pushUsers,
WhitelistActionsUser: enablePushWhitelist && form.PushWhitelistActionsUser,
CanForcePush: form.EnablePush && form.EnableForcePush,
EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
EnableForcePushAllowlist: enableForcePushAllowlist,
ForcePushAllowlistTeamIDs: forcePushTeams,
ForcePushAllowlistUserIDs: forcePushUsers,
ForcePushAllowlistActionsUser: enableForcePushAllowlist && form.ForcePushAllowlistActionsUser,
EnableMergeWhitelist: form.EnableMergeWhitelist,
MergeWhitelistTeamIDs: mergeTeams,
MergeWhitelistUserIDs: mergeUsers,
MergeWhitelistActionsUser: form.EnableMergeWhitelist && form.MergeWhitelistActionsUser,
EnableStatusCheck: form.EnableStatusCheck,
StatusCheckContexts: form.StatusCheckContexts,
RequiredApprovals: form.RequiredApprovals,
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
ApprovalsWhitelistTeamIDs: approvalsTeams,
ApprovalsWhitelistUserIDs: approvalsUsers,
BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
@@ -310,6 +396,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.WhitelistTeamIDs = ids
}
if form.PushWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames)
if !ok {
return
}
rule.WhitelistUserIDs = ids
}
if form.PushWhitelistActionsUser != nil {
rule.WhitelistActionsUser = *form.PushWhitelistActionsUser
}
if form.EnableForcePush != nil {
rule.CanForcePush = *form.EnableForcePush
}
@@ -323,6 +419,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.ForcePushAllowlistTeamIDs = ids
}
if form.ForcePushAllowlistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames)
if !ok {
return
}
rule.ForcePushAllowlistUserIDs = ids
}
if form.ForcePushAllowlistActionsUser != nil {
rule.ForcePushAllowlistActionsUser = *form.ForcePushAllowlistActionsUser
}
if form.EnableMergeWhitelist != nil {
rule.EnableMergeWhitelist = *form.EnableMergeWhitelist
}
@@ -333,6 +439,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.MergeWhitelistTeamIDs = ids
}
if form.MergeWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames)
if !ok {
return
}
rule.MergeWhitelistUserIDs = ids
}
if form.MergeWhitelistActionsUser != nil {
rule.MergeWhitelistActionsUser = *form.MergeWhitelistActionsUser
}
if form.EnableStatusCheck != nil {
rule.EnableStatusCheck = *form.EnableStatusCheck
}
@@ -352,6 +468,13 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.ApprovalsWhitelistTeamIDs = ids
}
if form.ApprovalsWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames)
if !ok {
return
}
rule.ApprovalsWhitelistUserIDs = ids
}
if form.BlockOnRejectedReviews != nil {
rule.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
}
+6
View File
@@ -156,6 +156,12 @@ type swaggerParameterBodies struct {
// in:body
EditBranchProtectionOption api.EditBranchProtectionOption
// in:body
CreateOrgBranchProtectionOption api.CreateOrgBranchProtectionOption
// in:body
EditOrgBranchProtectionOption api.EditOrgBranchProtectionOption
// in:body
UpdateBranchProtectionPriories api.UpdateBranchProtectionPriories
+14
View File
@@ -41,3 +41,17 @@ type swaggerResponseOrganizationPermissions struct {
// in:body
Body api.OrganizationPermissions `json:"body"`
}
// OrgBranchProtection
// swagger:response OrgBranchProtection
type swaggerResponseOrgBranchProtection struct {
// in:body
Body api.OrgBranchProtection `json:"body"`
}
// OrgBranchProtectionList
// swagger:response OrgBranchProtectionList
type swaggerResponseOrgBranchProtectionList struct {
// in:body
Body []api.OrgBranchProtection `json:"body"`
}
+697
View File
@@ -21518,6 +21518,192 @@
}
}
}
},
"/orgs/{org}/branch_protections": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "List an organization's branch protection rules",
"operationId": "orgListBranchProtections",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/OrgBranchProtectionList"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Create an org-level branch protection rule",
"operationId": "orgCreateBranchProtection",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/CreateOrgBranchProtectionOption"
}
}
],
"responses": {
"201": {
"$ref": "#/responses/OrgBranchProtection"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
},
"/orgs/{org}/branch_protections/{name}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Get a specific org-level branch protection rule",
"operationId": "orgGetBranchProtection",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the branch protection rule",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/OrgBranchProtection"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"delete": {
"tags": [
"organization"
],
"summary": "Delete an org-level branch protection rule",
"operationId": "orgDeleteBranchProtection",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the branch protection rule",
"name": "name",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"404": {
"$ref": "#/responses/notFound"
}
}
},
"patch": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Edit an org-level branch protection rule. Only fields that are set will be changed",
"operationId": "orgEditBranchProtection",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the branch protection rule",
"name": "name",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/EditOrgBranchProtectionOption"
}
}
],
"responses": {
"200": {
"$ref": "#/responses/OrgBranchProtection"
},
"404": {
"$ref": "#/responses/notFound"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
}
},
"definitions": {
@@ -30266,6 +30452,502 @@
}
},
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"CreateOrgBranchProtectionOption": {
"description": "CreateOrgBranchProtectionOption options for creating an org-level branch protection",
"type": "object",
"properties": {
"approvals_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistUsernames"
},
"priority": {
"type": "integer",
"format": "int64",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"type": "integer",
"format": "int64",
"x-go-name": "RequiredApprovals"
},
"rule_name": {
"type": "string",
"x-go-name": "RuleName"
},
"status_check_contexts": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
}
},
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"EditOrgBranchProtectionOption": {
"description": "EditOrgBranchProtectionOption options for editing an org-level branch protection",
"type": "object",
"properties": {
"approvals_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistUsernames"
},
"priority": {
"type": "integer",
"format": "int64",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"type": "integer",
"format": "int64",
"x-go-name": "RequiredApprovals"
},
"status_check_contexts": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
}
},
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"OrgBranchProtection": {
"description": "OrgBranchProtection represents an org-level branch protection ruleset",
"type": "object",
"properties": {
"approvals_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"created_at": {
"type": "string",
"format": "date-time",
"x-go-name": "Created"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushAllowlistUsernames"
},
"id": {
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "MergeWhitelistUsernames"
},
"org_id": {
"type": "integer",
"format": "int64",
"x-go-name": "OrgID"
},
"priority": {
"type": "integer",
"format": "int64",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"type": "integer",
"format": "int64",
"x-go-name": "RequiredApprovals"
},
"rule_name": {
"type": "string",
"x-go-name": "RuleName"
},
"status_check_contexts": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
},
"updated_at": {
"type": "string",
"format": "date-time",
"x-go-name": "Updated"
}
},
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
}
},
"responses": {
@@ -31404,6 +32086,21 @@
"type": "string"
}
}
},
"OrgBranchProtection": {
"description": "OrgBranchProtection",
"schema": {
"$ref": "#/definitions/OrgBranchProtection"
}
},
"OrgBranchProtectionList": {
"description": "OrgBranchProtectionList",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/OrgBranchProtection"
}
}
}
},
"securityDefinitions": {
+709
View File
@@ -911,6 +911,29 @@
},
"description": "OAuth2ApplicationList represents a list of OAuth2 applications."
},
"OrgBranchProtection": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OrgBranchProtection"
}
}
},
"description": "OrgBranchProtection"
},
"OrgBranchProtectionList": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/OrgBranchProtection"
},
"type": "array"
}
}
},
"description": "OrgBranchProtectionList"
},
"Organization": {
"content": {
"application/json": {
@@ -4264,6 +4287,166 @@
"type": "object",
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"CreateOrgBranchProtectionOption": {
"description": "CreateOrgBranchProtectionOption options for creating an org-level branch protection",
"properties": {
"approvals_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistUsernames"
},
"priority": {
"format": "int64",
"type": "integer",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"format": "int64",
"type": "integer",
"x-go-name": "RequiredApprovals"
},
"rule_name": {
"type": "string",
"x-go-name": "RuleName"
},
"status_check_contexts": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
}
},
"type": "object",
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"CreateOrgOption": {
"description": "CreateOrgOption options for creating an organization",
"properties": {
@@ -5431,6 +5614,162 @@
"type": "object",
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"EditOrgBranchProtectionOption": {
"description": "EditOrgBranchProtectionOption options for editing an org-level branch protection",
"properties": {
"approvals_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistUsernames"
},
"priority": {
"format": "int64",
"type": "integer",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"format": "int64",
"type": "integer",
"x-go-name": "RequiredApprovals"
},
"status_check_contexts": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
}
},
"type": "object",
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"EditOrgOption": {
"description": "EditOrgOption options for editing an organization",
"properties": {
@@ -7900,6 +8239,186 @@
],
"type": "string"
},
"OrgBranchProtection": {
"description": "OrgBranchProtection represents an org-level branch protection ruleset",
"properties": {
"approvals_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistTeams"
},
"approvals_whitelist_username": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ApprovalsWhitelistUsernames"
},
"block_admin_merge_override": {
"type": "boolean",
"x-go-name": "BlockAdminMergeOverride"
},
"block_on_official_review_requests": {
"type": "boolean",
"x-go-name": "BlockOnOfficialReviewRequests"
},
"block_on_outdated_branch": {
"type": "boolean",
"x-go-name": "BlockOnOutdatedBranch"
},
"block_on_rejected_reviews": {
"type": "boolean",
"x-go-name": "BlockOnRejectedReviews"
},
"created_at": {
"format": "date-time",
"type": "string",
"x-go-name": "Created"
},
"dismiss_stale_approvals": {
"type": "boolean",
"x-go-name": "DismissStaleApprovals"
},
"enable_approvals_whitelist": {
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_allowlist": {
"type": "boolean",
"x-go-name": "EnableForcePushAllowlist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
},
"enable_push": {
"type": "boolean",
"x-go-name": "EnablePush"
},
"enable_push_whitelist": {
"type": "boolean",
"x-go-name": "EnablePushWhitelist"
},
"enable_status_check": {
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_allowlist_actions_user": {
"type": "boolean",
"x-go-name": "ForcePushAllowlistActionsUser"
},
"force_push_allowlist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistTeams"
},
"force_push_allowlist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "ForcePushAllowlistUsernames"
},
"id": {
"format": "int64",
"type": "integer",
"x-go-name": "ID"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
},
"merge_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "MergeWhitelistActionsUser"
},
"merge_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistTeams"
},
"merge_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "MergeWhitelistUsernames"
},
"org_id": {
"format": "int64",
"type": "integer",
"x-go-name": "OrgID"
},
"priority": {
"format": "int64",
"type": "integer",
"x-go-name": "Priority"
},
"protected_file_patterns": {
"type": "string",
"x-go-name": "ProtectedFilePatterns"
},
"push_whitelist_actions_user": {
"type": "boolean",
"x-go-name": "PushWhitelistActionsUser"
},
"push_whitelist_teams": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistTeams"
},
"push_whitelist_usernames": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "PushWhitelistUsernames"
},
"require_signed_commits": {
"type": "boolean",
"x-go-name": "RequireSignedCommits"
},
"required_approvals": {
"format": "int64",
"type": "integer",
"x-go-name": "RequiredApprovals"
},
"rule_name": {
"type": "string",
"x-go-name": "RuleName"
},
"status_check_contexts": {
"items": {
"type": "string"
},
"type": "array",
"x-go-name": "StatusCheckContexts"
},
"unprotected_file_patterns": {
"type": "string",
"x-go-name": "UnprotectedFilePatterns"
},
"updated_at": {
"format": "date-time",
"type": "string",
"x-go-name": "Updated"
}
},
"type": "object",
"x-go-package": "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
},
"Organization": {
"description": "Organization represents an organization",
"properties": {
@@ -13484,6 +14003,196 @@
]
}
},
"/orgs/{org}/branch_protections": {
"get": {
"operationId": "orgListBranchProtections",
"parameters": [
{
"description": "name of the organization",
"in": "path",
"name": "org",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/OrgBranchProtectionList"
},
"404": {
"$ref": "#/components/responses/notFound"
}
},
"summary": "List an organization's branch protection rules",
"tags": [
"organization"
]
},
"post": {
"operationId": "orgCreateBranchProtection",
"parameters": [
{
"description": "name of the organization",
"in": "path",
"name": "org",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateOrgBranchProtectionOption"
}
}
},
"x-originalParamName": "body"
},
"responses": {
"201": {
"$ref": "#/components/responses/OrgBranchProtection"
},
"403": {
"$ref": "#/components/responses/forbidden"
},
"404": {
"$ref": "#/components/responses/notFound"
},
"422": {
"$ref": "#/components/responses/validationError"
}
},
"summary": "Create an org-level branch protection rule",
"tags": [
"organization"
]
}
},
"/orgs/{org}/branch_protections/{name}": {
"delete": {
"operationId": "orgDeleteBranchProtection",
"parameters": [
{
"description": "name of the organization",
"in": "path",
"name": "org",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "name of the branch protection rule",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"$ref": "#/components/responses/empty"
},
"404": {
"$ref": "#/components/responses/notFound"
}
},
"summary": "Delete an org-level branch protection rule",
"tags": [
"organization"
]
},
"get": {
"operationId": "orgGetBranchProtection",
"parameters": [
{
"description": "name of the organization",
"in": "path",
"name": "org",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "name of the branch protection rule",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"$ref": "#/components/responses/OrgBranchProtection"
},
"404": {
"$ref": "#/components/responses/notFound"
}
},
"summary": "Get a specific org-level branch protection rule",
"tags": [
"organization"
]
},
"patch": {
"operationId": "orgEditBranchProtection",
"parameters": [
{
"description": "name of the organization",
"in": "path",
"name": "org",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "name of the branch protection rule",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EditOrgBranchProtectionOption"
}
}
},
"x-originalParamName": "body"
},
"responses": {
"200": {
"$ref": "#/components/responses/OrgBranchProtection"
},
"404": {
"$ref": "#/components/responses/notFound"
},
"422": {
"$ref": "#/components/responses/validationError"
}
},
"summary": "Edit an org-level branch protection rule. Only fields that are set will be changed",
"tags": [
"organization"
]
}
},
"/orgs/{org}/hooks": {
"get": {
"operationId": "orgListHooks",
+1 -1
View File
@@ -25,7 +25,7 @@ Custom CSS themes at `/var/lib/gitea/custom/public/assets/css/`:
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+1 -1
View File
@@ -126,7 +126,7 @@ When a field definition is deleted, all associated values in `custom_field_value
| System | Relationship |
|--------|-------------|
| Update Server | Repo-scoped custom fields with specific names (Extension Name, Display Name, etc.) are read by the update feed generators as the highest-priority metadata source. |
| Manifest Settings | Manifest fields follow the mokocli schema and are separate from custom fields. Custom fields are user-defined; manifest fields are standardized. |
| Manifest Settings | Manifest fields follow the moko-platform schema and are separate from custom fields. Custom fields are user-defined; manifest fields are standardized. |
| Issue Statuses | Custom statuses are a separate feature with their own dedicated table and UI, not implemented as custom fields. |
---
+1 -1
View File
@@ -41,7 +41,7 @@ Located at `/var/lib/gitea/custom/`:
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+2 -2
View File
@@ -309,8 +309,8 @@ Packages can be **archived** instead of permanently deleted. This is the recomme
In **Organization Settings → Licensing & Update Streams**, under **Extension Metadata**:
1. Set the **Platform** (Joomla, Dolibarr, WordPress, etc.)
2. Set the **Extension Name** (e.g., `pkg_mokosuite`) — this becomes the `<element>` in the XML feed
3. Set the **Display Name** (e.g., "Package - MokoSuite") — shown in Joomla update manager
2. Set the **Extension Name** (e.g., `pkg_mokowaas`) — this becomes the `<element>` in the XML feed
3. Set the **Display Name** (e.g., "Package - MokoWaaS") — shown in Joomla update manager
4. Set the **Extension Type** (component, module, plugin, package, template, library)
5. Set the **Target Version** regex (e.g., `(5|6)\..*` for Joomla 5 and 6)
6. Set the **PHP Minimum** if applicable (e.g., `8.1`)
+5 -5
View File
@@ -7,7 +7,7 @@ The manifest settings feature provides a centralized way to store and manage pro
Each repository can have a manifest that describes:
- **Identity** — project name, organization, description, version, and license
- **Governance** — platform type, mokocli standards version, and standards source URL
- **Governance** — platform type, moko-platform standards version, and standards source URL
- **Build** — language, package type, and entry point
These settings replace the legacy `.mokogitea/manifest.xml` file-based approach.
@@ -35,7 +35,7 @@ If a field already has a value in the database (e.g., from org-level custom fiel
## REST API
The manifest API allows Actions workflows and the mokocli CLI to read and write manifest settings programmatically.
The manifest API allows Actions workflows and the moko-platform CLI to read and write manifest settings programmatically.
### Get Manifest
@@ -57,7 +57,7 @@ Returns the current manifest settings. If no manifest has been saved, returns de
"license_name": "GNU General Public License v3",
"platform": "go",
"standards_version": "05.00.00",
"standards_source": "https://code.mokoconsulting.tech/MokoConsulting/mokocli",
"standards_source": "https://code.mokoconsulting.tech/MokoConsulting/moko-platform",
"language": "Go",
"package_type": "application",
"entry_point": "./"
@@ -101,8 +101,8 @@ Manifest settings are stored in the `repo_manifest` table (migration v347). One
| System | Relationship |
|--------|-------------|
| Update Server | The update server generators read from both manifest settings and update_stream_config. Manifest provides identity metadata; update_stream_config provides feed-specific settings. |
| Custom Fields | Repo-scoped custom fields (org settings) are separate from manifest fields. Custom fields are user-defined; manifest fields follow the mokocli schema. |
| mokocli CLI | The CLI reads manifest settings via the API for version bumping, build decisions, and cross-repo syncing (see issue #505). |
| Custom Fields | Repo-scoped custom fields (org settings) are separate from manifest fields. Custom fields are user-defined; manifest fields follow the moko-platform schema. |
| moko-platform CLI | The CLI reads manifest settings via the API for version bumping, build decisions, and cross-repo syncing (see issue #505). |
---
+1 -1
View File
@@ -195,7 +195,7 @@ curl -X POST -H "Authorization: token TOKEN" \
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+2 -2
View File
@@ -23,9 +23,9 @@
## In Progress
- Rename mokocli to MokoCLI
- Rename moko-platform to MokoPlatform
- Granular role-based permissions for all features (#9)
- Wire mokocli CLI to manifest API (#505)
- Wire moko-platform CLI to manifest API (#505)
- Bulk migrate remaining 41 flat wikis to folders
## Planned