mirror of
https://github.com/Smaug123/gitea-repo-config
synced 2025-10-08 17:08:40 +00:00
Add branch protection (#31)
This commit is contained in:
10
Gitea.Declarative.Lib/Async.fs
Normal file
10
Gitea.Declarative.Lib/Async.fs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Gitea.Declarative
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
module Async =
|
||||||
|
|
||||||
|
let map f a =
|
||||||
|
async {
|
||||||
|
let! a = a
|
||||||
|
return f a
|
||||||
|
}
|
@@ -34,6 +34,18 @@ type PushMirror =
|
|||||||
GitHubAddress = Uri s.GitHubAddress
|
GitHubAddress = Uri s.GitHubAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProtectedBranch =
|
||||||
|
{
|
||||||
|
BranchName : string
|
||||||
|
BlockOnOutdatedBranch : bool option
|
||||||
|
}
|
||||||
|
|
||||||
|
static member OfSerialised (s : SerialisedProtectedBranch) : ProtectedBranch =
|
||||||
|
{
|
||||||
|
BranchName = s.BranchName
|
||||||
|
BlockOnOutdatedBranch = Option.ofNullable s.BlockOnOutdatedBranch
|
||||||
|
}
|
||||||
|
|
||||||
type NativeRepo =
|
type NativeRepo =
|
||||||
{
|
{
|
||||||
DefaultBranch : string
|
DefaultBranch : string
|
||||||
@@ -51,6 +63,7 @@ type NativeRepo =
|
|||||||
AllowRebaseExplicit : bool option
|
AllowRebaseExplicit : bool option
|
||||||
AllowMergeCommits : bool option
|
AllowMergeCommits : bool option
|
||||||
Mirror : PushMirror option
|
Mirror : PushMirror option
|
||||||
|
ProtectedBranches : ProtectedBranch Set
|
||||||
}
|
}
|
||||||
|
|
||||||
static member Default : NativeRepo =
|
static member Default : NativeRepo =
|
||||||
@@ -70,6 +83,7 @@ type NativeRepo =
|
|||||||
AllowRebaseExplicit = Some false
|
AllowRebaseExplicit = Some false
|
||||||
AllowMergeCommits = Some false
|
AllowMergeCommits = Some false
|
||||||
Mirror = None
|
Mirror = None
|
||||||
|
ProtectedBranches = Set.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
member this.OverrideDefaults () =
|
member this.OverrideDefaults () =
|
||||||
@@ -93,6 +107,7 @@ type NativeRepo =
|
|||||||
AllowRebaseExplicit = this.AllowRebaseExplicit |> Option.orElse NativeRepo.Default.AllowRebaseExplicit
|
AllowRebaseExplicit = this.AllowRebaseExplicit |> Option.orElse NativeRepo.Default.AllowRebaseExplicit
|
||||||
AllowMergeCommits = this.AllowMergeCommits |> Option.orElse NativeRepo.Default.AllowMergeCommits
|
AllowMergeCommits = this.AllowMergeCommits |> Option.orElse NativeRepo.Default.AllowMergeCommits
|
||||||
Mirror = this.Mirror
|
Mirror = this.Mirror
|
||||||
|
ProtectedBranches = this.ProtectedBranches // TODO should this replace null with empty?
|
||||||
}
|
}
|
||||||
|
|
||||||
static member internal OfSerialised (s : SerialisedNativeRepo) =
|
static member internal OfSerialised (s : SerialisedNativeRepo) =
|
||||||
@@ -112,6 +127,10 @@ type NativeRepo =
|
|||||||
AllowRebaseExplicit = s.AllowRebaseExplicit |> Option.ofNullable
|
AllowRebaseExplicit = s.AllowRebaseExplicit |> Option.ofNullable
|
||||||
AllowMergeCommits = s.AllowMergeCommits |> Option.ofNullable
|
AllowMergeCommits = s.AllowMergeCommits |> Option.ofNullable
|
||||||
Mirror = s.Mirror |> Option.ofNullable |> Option.map PushMirror.OfSerialised
|
Mirror = s.Mirror |> Option.ofNullable |> Option.map PushMirror.OfSerialised
|
||||||
|
ProtectedBranches =
|
||||||
|
match s.ProtectedBranches with
|
||||||
|
| null -> Set.empty
|
||||||
|
| a -> a |> Seq.map ProtectedBranch.OfSerialised |> Set.ofSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitHubRepo =
|
type GitHubRepo =
|
||||||
@@ -145,7 +164,7 @@ type Repo =
|
|||||||
}
|
}
|
||||||
|
|
||||||
static member Render (client : Gitea.Client) (u : Gitea.Repository) : Repo Async =
|
static member Render (client : Gitea.Client) (u : Gitea.Repository) : Repo Async =
|
||||||
if not (String.IsNullOrEmpty u.OriginalUrl) then
|
if u.Mirror = Some true && not (String.IsNullOrEmpty u.OriginalUrl) then
|
||||||
{
|
{
|
||||||
Description = u.Description
|
Description = u.Description
|
||||||
GitHub =
|
GitHub =
|
||||||
@@ -166,6 +185,10 @@ type Repo =
|
|||||||
elif mirror.Length = 1 then Some mirror.[0]
|
elif mirror.Length = 1 then Some mirror.[0]
|
||||||
else failwith "Multiple mirrors not supported yet"
|
else failwith "Multiple mirrors not supported yet"
|
||||||
|
|
||||||
|
let! branchProtections =
|
||||||
|
client.RepoListBranchProtection (u.Owner.LoginName, u.FullName)
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -194,6 +217,15 @@ type Repo =
|
|||||||
GitHubAddress = Uri m.RemoteAddress
|
GitHubAddress = Uri m.RemoteAddress
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
ProtectedBranches =
|
||||||
|
branchProtections
|
||||||
|
|> Seq.map (fun bp ->
|
||||||
|
{
|
||||||
|
BranchName = bp.BranchName
|
||||||
|
BlockOnOutdatedBranch = bp.BlockOnOutdatedBranch
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> Set.ofSeq
|
||||||
}
|
}
|
||||||
|> Some
|
|> Some
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="AssemblyInfo.fs" />
|
<Compile Include="AssemblyInfo.fs" />
|
||||||
<Compile Include="Map.fs" />
|
<Compile Include="Map.fs" />
|
||||||
|
<Compile Include="Async.fs" />
|
||||||
<Compile Include="GiteaClient.fs" />
|
<Compile Include="GiteaClient.fs" />
|
||||||
<Compile Include="Domain.fs" />
|
<Compile Include="Domain.fs" />
|
||||||
<Compile Include="SerialisedConfigSchema.fs" />
|
<Compile Include="SerialisedConfigSchema.fs" />
|
||||||
|
@@ -232,11 +232,18 @@ module Gitea =
|
|||||||
}
|
}
|
||||||
| AlignmentError.ConfigurationDiffers (desired, actual) ->
|
| AlignmentError.ConfigurationDiffers (desired, actual) ->
|
||||||
match desired.GitHub, actual.GitHub with
|
match desired.GitHub, actual.GitHub with
|
||||||
| None, Some _
|
| None, Some gitHub ->
|
||||||
|
async {
|
||||||
|
logger.LogCritical (
|
||||||
|
"Unable to reconcile the desire to move a repo from GitHub-based to Gitea-based. This feature is not exposed on the Gitea API. You must manually convert the following repo to a normal repository first: {User}:{Repo}.",
|
||||||
|
user,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
}
|
||||||
| Some _, None ->
|
| Some _, None ->
|
||||||
async {
|
async {
|
||||||
logger.LogError (
|
logger.LogError (
|
||||||
"Unable to reconcile the desire to move a repo from Gitea to GitHub or vice versa: {User}, {Repo}.",
|
"Unable to reconcile the desire to move a repo from Gitea-based to GitHub-based: {User}:{Repo}.",
|
||||||
user,
|
user,
|
||||||
r
|
r
|
||||||
)
|
)
|
||||||
@@ -474,6 +481,86 @@ module Gitea =
|
|||||||
async { logger.LogCritical ("Push mirror on {User}:{Repo} differs.", user, r) }
|
async { logger.LogCritical ("Push mirror on {User}:{Repo} differs.", user, r) }
|
||||||
else
|
else
|
||||||
async.Return ()
|
async.Return ()
|
||||||
|
|
||||||
|
do!
|
||||||
|
// TODO: lift this out to a function and then put it into the new-repo flow too
|
||||||
|
let extraActualProtected =
|
||||||
|
Set.difference actual.ProtectedBranches desired.ProtectedBranches
|
||||||
|
|
||||||
|
let extraDesiredProtected =
|
||||||
|
Set.difference desired.ProtectedBranches actual.ProtectedBranches
|
||||||
|
|
||||||
|
Seq.append
|
||||||
|
(Seq.map Choice1Of2 extraActualProtected)
|
||||||
|
(Seq.map Choice2Of2 extraDesiredProtected)
|
||||||
|
|> Seq.groupBy (fun b ->
|
||||||
|
match b with
|
||||||
|
| Choice1Of2 b -> b.BranchName
|
||||||
|
| Choice2Of2 b -> b.BranchName
|
||||||
|
)
|
||||||
|
|> Seq.map (fun (key, values) ->
|
||||||
|
match Seq.toList values with
|
||||||
|
| [] -> failwith "can't have appeared no times in a groupBy"
|
||||||
|
| [ Choice1Of2 x ] ->
|
||||||
|
// This is an extra rule; delete it
|
||||||
|
async {
|
||||||
|
logger.LogInformation (
|
||||||
|
"Deleting branch protection rule {BranchProtection} on {User}:{Repo}",
|
||||||
|
x.BranchName,
|
||||||
|
user,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
|
||||||
|
let! _ =
|
||||||
|
client.RepoDeleteBranchProtection (user, r, x.BranchName)
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
| [ Choice2Of2 y ] ->
|
||||||
|
// This is an absent rule; add it
|
||||||
|
async {
|
||||||
|
logger.LogInformation (
|
||||||
|
"Creating branch protection rule {BranchProtection} on {User}:{Repo}",
|
||||||
|
y.BranchName,
|
||||||
|
user,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
|
||||||
|
let s = Gitea.CreateBranchProtectionOption ()
|
||||||
|
s.BranchName <- y.BranchName
|
||||||
|
s.RuleName <- y.BranchName
|
||||||
|
s.BlockOnOutdatedBranch <- y.BlockOnOutdatedBranch
|
||||||
|
let! _ = client.RepoCreateBranchProtection (user, r, s) |> Async.AwaitTask
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
| [ Choice1Of2 x ; Choice2Of2 y ]
|
||||||
|
| [ Choice2Of2 y ; Choice1Of2 x ] ->
|
||||||
|
// Need to reconcile the two; the Choice2Of2 is what we want to keep
|
||||||
|
async {
|
||||||
|
logger.LogInformation (
|
||||||
|
"Reconciling branch protection rule {BranchProtection} on {User}:{Repo}",
|
||||||
|
y.BranchName,
|
||||||
|
user,
|
||||||
|
r
|
||||||
|
)
|
||||||
|
|
||||||
|
let s = Gitea.EditBranchProtectionOption ()
|
||||||
|
s.BlockOnOutdatedBranch <- y.BlockOnOutdatedBranch
|
||||||
|
|
||||||
|
let! _ =
|
||||||
|
client.RepoEditBranchProtection (user, r, y.BranchName, s)
|
||||||
|
|> Async.AwaitTask
|
||||||
|
|
||||||
|
return ()
|
||||||
|
}
|
||||||
|
| [ Choice1Of2 _ ; Choice1Of2 _ ]
|
||||||
|
| [ Choice2Of2 _ ; Choice2Of2 _ ] ->
|
||||||
|
failwith "can't have the same choice appearing twice"
|
||||||
|
| _ :: _ :: _ :: _ -> failwith "can't have appeared three times"
|
||||||
|
)
|
||||||
|
|> Async.Parallel
|
||||||
|
|> Async.map (Array.iter id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@@ -188,6 +188,16 @@
|
|||||||
"$ref": "#/definitions/SerialisedPushMirror"
|
"$ref": "#/definitions/SerialisedPushMirror"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"protectedBranches": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"description": "Protected branch configuration",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SerialisedProtectedBranch"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -195,12 +205,28 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Information about a repo that is to be created on Gitea without syncing from GitHub.",
|
"description": "Information about a repo that is to be created on Gitea without syncing from GitHub.",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"gitHubAddress"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"gitHubAddress": {
|
"gitHubAddress": {
|
||||||
"type": [
|
"type": "string"
|
||||||
"null",
|
}
|
||||||
"string"
|
}
|
||||||
]
|
},
|
||||||
|
"SerialisedProtectedBranch": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Information about a repo that is to be created on Gitea without syncing from GitHub.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"branchName"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"branchName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"blockOnOutdatedBranch": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,9 +13,22 @@ type SerialisedMergeStyle = string
|
|||||||
[<Description "Information about a repo that is to be created on Gitea without syncing from GitHub.">]
|
[<Description "Information about a repo that is to be created on Gitea without syncing from GitHub.">]
|
||||||
type SerialisedPushMirror =
|
type SerialisedPushMirror =
|
||||||
{
|
{
|
||||||
|
[<JsonProperty(Required = Required.Always)>]
|
||||||
GitHubAddress : string
|
GitHubAddress : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
[<Struct>]
|
||||||
|
[<CLIMutable>]
|
||||||
|
[<Description "Information about a repo that is to be created on Gitea without syncing from GitHub.">]
|
||||||
|
type SerialisedProtectedBranch =
|
||||||
|
{
|
||||||
|
[<JsonProperty(Required = Required.Always)>]
|
||||||
|
BranchName : string
|
||||||
|
[<JsonProperty(Required = Required.DisallowNull)>]
|
||||||
|
BlockOnOutdatedBranch : Nullable<bool>
|
||||||
|
}
|
||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
@@ -67,6 +80,9 @@ type internal SerialisedNativeRepo =
|
|||||||
[<JsonProperty(Required = Required.DisallowNull)>]
|
[<JsonProperty(Required = Required.DisallowNull)>]
|
||||||
[<Description "Configure a GitHub push mirror to sync this repo to">]
|
[<Description "Configure a GitHub push mirror to sync this repo to">]
|
||||||
Mirror : Nullable<SerialisedPushMirror>
|
Mirror : Nullable<SerialisedPushMirror>
|
||||||
|
[<JsonProperty(Required = Required.Default)>]
|
||||||
|
[<Description "Protected branch configuration">]
|
||||||
|
ProtectedBranches : SerialisedProtectedBranch array
|
||||||
}
|
}
|
||||||
|
|
||||||
[<Struct>]
|
[<Struct>]
|
||||||
|
@@ -42,6 +42,19 @@
|
|||||||
"gitHubAddress": "https://github.com/MyName/repo-name-3"
|
"gitHubAddress": "https://github.com/MyName/repo-name-3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"new-repo-mirrored-with-branches": {
|
||||||
|
"description": "A repo that's created directly on this Gitea and mirrored to GitHub",
|
||||||
|
"native": {
|
||||||
|
"defaultBranch": "main",
|
||||||
|
"private": false,
|
||||||
|
"mirror": {
|
||||||
|
"gitHubAddress": "https://github.com/MyName/repo-name-3"
|
||||||
|
},
|
||||||
|
"protectedBranches": [
|
||||||
|
{ "branchName": "main" }
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user