feat(git-control): add Git repository and branch access control features
- Introduced new scripts for managing Gitea repository permissions, including branch protection, user access, and organization-wide settings. - Added documentation for the git-control skill, detailing API usage and capabilities. - Included .gitignore to prevent committing sensitive configuration files. - Created references for Gitea API endpoints related to branch protection and user permissions.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Branch protection: lock, unlock, or merge-only
|
||||
# lock = no push, no merge | unlock = remove protection | merge-only = no direct push, PR merge allowed
|
||||
|
||||
param(
|
||||
[string]$ApiToken = $env:GITEA_TOKEN,
|
||||
[string]$GiteaBaseUrl = "http://afe.git:3000",
|
||||
[string]$Owner,
|
||||
[string]$Repo,
|
||||
[string]$BranchName,
|
||||
[ValidateSet("lock", "unlock", "merge-only")]
|
||||
[string]$Action = "lock"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ConfigPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config.local.json"
|
||||
if (-not $ApiToken -and (Test-Path $ConfigPath)) {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
$ApiToken = $cfg.ApiToken
|
||||
if ($cfg.GiteaBaseUrl) { $GiteaBaseUrl = $cfg.GiteaBaseUrl }
|
||||
}
|
||||
if (-not $ApiToken) { Write-Host "Error: ApiToken required"; exit 1 }
|
||||
if (-not $Owner -or -not $Repo -or -not $BranchName) { Write-Host "Error: Owner, Repo, BranchName required"; exit 1 }
|
||||
|
||||
$ApiBase = "$GiteaBaseUrl/api/v1"
|
||||
$ReposPath = "repos/$Owner/$Repo"
|
||||
$Headers = @{ "Authorization" = "token $ApiToken"; "Content-Type" = "application/json" }
|
||||
|
||||
# Get existing protections
|
||||
$Existing = @()
|
||||
try {
|
||||
$r = Invoke-RestMethod -Uri "$ApiBase/$ReposPath/branch_protections" -Headers $Headers -Method Get
|
||||
$Existing = if ($r -is [array]) { $r } else { @($r) }
|
||||
} catch {
|
||||
if ($_.Exception.Response.StatusCode -eq 404) { Write-Host "Repo not found"; exit 1 }
|
||||
throw
|
||||
}
|
||||
|
||||
$Match = $Existing | Where-Object { $_.rule_name -eq $BranchName -or $_.branch_name -eq $BranchName } | Select-Object -First 1
|
||||
$RuleName = if ($Match) { if ($Match.rule_name) { $Match.rule_name } else { $BranchName } } else { $BranchName }
|
||||
|
||||
# Delete existing rule
|
||||
if ($Match) {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBase/$ReposPath/branch_protections/$([uri]::EscapeDataString($RuleName))" -Headers $Headers -Method Delete | Out-Null
|
||||
} catch { Write-Host "Warning: Delete failed: $_" }
|
||||
}
|
||||
|
||||
if ($Action -eq "unlock") {
|
||||
Write-Host "Branch $BranchName unlocked (protection removed)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Create new rule
|
||||
$Body = @{
|
||||
branch_name = $BranchName
|
||||
enable_push = if ($Action -eq "merge-only") { $false } else { $false }
|
||||
enable_push_whitelist = $false
|
||||
enable_merge_whitelist = if ($Action -eq "lock") { $true } else { $false }
|
||||
merge_whitelist_usernames = @()
|
||||
merge_whitelist_teams = @()
|
||||
block_on_rejected_reviews = $false
|
||||
block_on_outdated_branch = $false
|
||||
dismiss_stale_approvals = $false
|
||||
require_signed_commits = $false
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBase/$ReposPath/branch_protections" -Headers $Headers -Method Post -Body $Body | Out-Null
|
||||
Write-Host "Branch ${BranchName}: $Action applied"
|
||||
} catch {
|
||||
Write-Host "FAIL: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# List organizations and repositories
|
||||
|
||||
param(
|
||||
[string]$ApiToken = $env:GITEA_TOKEN,
|
||||
[string]$GiteaBaseUrl = "http://afe.git:3000",
|
||||
[ValidateSet("list-orgs", "list-repos", "list-all-repos")]
|
||||
[string]$Action = "list-orgs",
|
||||
[string]$Org
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ConfigPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config.local.json"
|
||||
if (-not $ApiToken -and (Test-Path $ConfigPath)) {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
$ApiToken = $cfg.ApiToken
|
||||
if ($cfg.GiteaBaseUrl) { $GiteaBaseUrl = $cfg.GiteaBaseUrl }
|
||||
}
|
||||
if (-not $ApiToken) { Write-Host "Error: ApiToken required"; exit 1 }
|
||||
|
||||
$ApiBase = "$GiteaBaseUrl/api/v1"
|
||||
$Headers = @{ "Authorization" = "token $ApiToken"; "Content-Type" = "application/json" }
|
||||
|
||||
if ($Action -eq "list-orgs") {
|
||||
try {
|
||||
$orgs = Invoke-RestMethod -Uri "$ApiBase/admin/orgs?page=1&limit=100" -Headers $Headers -Method Get
|
||||
$orgs | ForEach-Object { Write-Host $_.username }
|
||||
} catch {
|
||||
if ($_.Exception.Response.StatusCode -eq 403) { Write-Host "Admin permission required for list-orgs"; exit 1 }
|
||||
throw
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Action -eq "list-repos") {
|
||||
if (-not $Org) { Write-Host "Error: -Org required for list-repos"; exit 1 }
|
||||
$repos = @()
|
||||
$page = 1
|
||||
do {
|
||||
$r = Invoke-RestMethod -Uri "$ApiBase/orgs/$Org/repos?page=$page&limit=50" -Headers $Headers -Method Get
|
||||
$arr = if ($r -is [array]) { $r } else { @($r) }
|
||||
$repos += $arr
|
||||
$page++
|
||||
} while ($arr.Count -eq 50)
|
||||
foreach ($repo in $repos) { Write-Output $repo.name }
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Action -eq "list-all-repos") {
|
||||
$orgs = Invoke-RestMethod -Uri "$ApiBase/admin/orgs?page=1&limit=100" -Headers $Headers -Method Get
|
||||
foreach ($o in $orgs) {
|
||||
$r = Invoke-RestMethod -Uri "$ApiBase/orgs/$($o.username)/repos?page=1&limit=100" -Headers $Headers -Method Get
|
||||
$r | ForEach-Object { Write-Host "$($o.username)/$($_.name)" }
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Apply branch protection to all repos in an organization
|
||||
|
||||
param(
|
||||
[string]$ApiToken = $env:GITEA_TOKEN,
|
||||
[string]$GiteaBaseUrl = "http://afe.git:3000",
|
||||
[string]$Org,
|
||||
[string]$BranchName,
|
||||
[ValidateSet("lock", "unlock")]
|
||||
[string]$Action = "lock"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ConfigPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config.local.json"
|
||||
if (-not $ApiToken -and (Test-Path $ConfigPath)) {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
$ApiToken = $cfg.ApiToken
|
||||
if ($cfg.GiteaBaseUrl) { $GiteaBaseUrl = $cfg.GiteaBaseUrl }
|
||||
}
|
||||
if (-not $ApiToken) { Write-Host "Error: ApiToken required"; exit 1 }
|
||||
if (-not $Org -or -not $BranchName) { Write-Host "Error: Org, BranchName required"; exit 1 }
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$BranchScript = Join-Path $ScriptDir "branch_protect.ps1"
|
||||
|
||||
$repos = @(& "$ScriptDir\list_orgs_repos.ps1" -ApiToken $ApiToken -GiteaBaseUrl $GiteaBaseUrl -Action list-repos -Org $Org)
|
||||
if (-not $repos -or $repos.Count -eq 0) { Write-Host "No repos found in org $Org"; exit 0 }
|
||||
|
||||
$count = 0
|
||||
foreach ($r in $repos) {
|
||||
if (-not $r) { continue }
|
||||
Write-Host "Processing $Org/$r..."
|
||||
& $BranchScript -ApiToken $ApiToken -GiteaBaseUrl $GiteaBaseUrl -Owner $Org -Repo $r -BranchName $BranchName -Action $Action
|
||||
$count++
|
||||
}
|
||||
Write-Host "Done. Processed $count repos."
|
||||
@@ -0,0 +1,35 @@
|
||||
# Revoke or grant a user's access across all repos in an organization
|
||||
# Uses collaborator API - only affects repos where user is direct collaborator
|
||||
|
||||
param(
|
||||
[string]$ApiToken = $env:GITEA_TOKEN,
|
||||
[string]$GiteaBaseUrl = "http://afe.git:3000",
|
||||
[string]$Org,
|
||||
[string]$Username,
|
||||
[ValidateSet("revoke", "read", "write")]
|
||||
[string]$Action = "revoke"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ConfigPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config.local.json"
|
||||
if (-not $ApiToken -and (Test-Path $ConfigPath)) {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
$ApiToken = $cfg.ApiToken
|
||||
if ($cfg.GiteaBaseUrl) { $GiteaBaseUrl = $cfg.GiteaBaseUrl }
|
||||
}
|
||||
if (-not $ApiToken) { Write-Host "Error: ApiToken required"; exit 1 }
|
||||
if (-not $Org -or -not $Username) { Write-Host "Error: Org, Username required"; exit 1 }
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Repos = @(& "$ScriptDir\list_orgs_repos.ps1" -ApiToken $ApiToken -GiteaBaseUrl $GiteaBaseUrl -Action list-repos -Org $Org)
|
||||
$UserScript = Join-Path $ScriptDir "user_permission.ps1"
|
||||
|
||||
$count = 0
|
||||
foreach ($Repo in $Repos) {
|
||||
if (-not $Repo) { continue }
|
||||
Write-Host "Processing $Org/$Repo..."
|
||||
$null = & $UserScript -ApiToken $ApiToken -GiteaBaseUrl $GiteaBaseUrl -Owner $Org -Repo $Repo -Username $Username -Action $Action 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { $count++ }
|
||||
}
|
||||
Write-Host "Done. Updated $count repos where $Username was collaborator."
|
||||
Write-Host "Note: Org repos may use team permissions. Check org teams for full control."
|
||||
@@ -0,0 +1,45 @@
|
||||
# Add, update, or revoke user permission on a repository
|
||||
|
||||
param(
|
||||
[string]$ApiToken = $env:GITEA_TOKEN,
|
||||
[string]$GiteaBaseUrl = "http://afe.git:3000",
|
||||
[string]$Owner,
|
||||
[string]$Repo,
|
||||
[string]$Username,
|
||||
[ValidateSet("read", "write", "admin", "revoke")]
|
||||
[string]$Action = "read"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ConfigPath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) "..\config.local.json"
|
||||
if (-not $ApiToken -and (Test-Path $ConfigPath)) {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
$ApiToken = $cfg.ApiToken
|
||||
if ($cfg.GiteaBaseUrl) { $GiteaBaseUrl = $cfg.GiteaBaseUrl }
|
||||
}
|
||||
if (-not $ApiToken) { Write-Host "Error: ApiToken required"; exit 1 }
|
||||
if (-not $Owner -or -not $Repo -or -not $Username) { Write-Host "Error: Owner, Repo, Username required"; exit 1 }
|
||||
|
||||
$ApiBase = "$GiteaBaseUrl/api/v1"
|
||||
$ReposPath = "repos/$Owner/$Repo"
|
||||
$Headers = @{ "Authorization" = "token $ApiToken"; "Content-Type" = "application/json" }
|
||||
|
||||
if ($Action -eq "revoke") {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBase/$ReposPath/collaborators/$Username" -Headers $Headers -Method Delete
|
||||
Write-Host "Revoked $Username from $Owner/$Repo"
|
||||
} catch {
|
||||
if ($_.Exception.Response.StatusCode -eq 404) { Write-Host "User not a collaborator or repo not found" }
|
||||
else { throw }
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
|
||||
$Body = @{ permission = $Action } | ConvertTo-Json
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$ApiBase/$ReposPath/collaborators/$Username" -Headers $Headers -Method Put -Body $Body
|
||||
Write-Host "Set $Username to $Action on $Owner/$Repo"
|
||||
} catch {
|
||||
Write-Host "FAIL: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user