mirror of
https://github.com/Smaug123/gitea-repo-config
synced 2025-10-08 08:58: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
|
||||
}
|
||||
|
||||
type ProtectedBranch =
|
||||
{
|
||||
BranchName : string
|
||||
BlockOnOutdatedBranch : bool option
|
||||
}
|
||||
|
||||
static member OfSerialised (s : SerialisedProtectedBranch) : ProtectedBranch =
|
||||
{
|
||||
BranchName = s.BranchName
|
||||
BlockOnOutdatedBranch = Option.ofNullable s.BlockOnOutdatedBranch
|
||||
}
|
||||
|
||||
type NativeRepo =
|
||||
{
|
||||
DefaultBranch : string
|
||||
@@ -51,6 +63,7 @@ type NativeRepo =
|
||||
AllowRebaseExplicit : bool option
|
||||
AllowMergeCommits : bool option
|
||||
Mirror : PushMirror option
|
||||
ProtectedBranches : ProtectedBranch Set
|
||||
}
|
||||
|
||||
static member Default : NativeRepo =
|
||||
@@ -70,6 +83,7 @@ type NativeRepo =
|
||||
AllowRebaseExplicit = Some false
|
||||
AllowMergeCommits = Some false
|
||||
Mirror = None
|
||||
ProtectedBranches = Set.empty
|
||||
}
|
||||
|
||||
member this.OverrideDefaults () =
|
||||
@@ -93,6 +107,7 @@ type NativeRepo =
|
||||
AllowRebaseExplicit = this.AllowRebaseExplicit |> Option.orElse NativeRepo.Default.AllowRebaseExplicit
|
||||
AllowMergeCommits = this.AllowMergeCommits |> Option.orElse NativeRepo.Default.AllowMergeCommits
|
||||
Mirror = this.Mirror
|
||||
ProtectedBranches = this.ProtectedBranches // TODO should this replace null with empty?
|
||||
}
|
||||
|
||||
static member internal OfSerialised (s : SerialisedNativeRepo) =
|
||||
@@ -112,6 +127,10 @@ type NativeRepo =
|
||||
AllowRebaseExplicit = s.AllowRebaseExplicit |> Option.ofNullable
|
||||
AllowMergeCommits = s.AllowMergeCommits |> Option.ofNullable
|
||||
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 =
|
||||
@@ -145,7 +164,7 @@ type Repo =
|
||||
}
|
||||
|
||||
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
|
||||
GitHub =
|
||||
@@ -166,6 +185,10 @@ type Repo =
|
||||
elif mirror.Length = 1 then Some mirror.[0]
|
||||
else failwith "Multiple mirrors not supported yet"
|
||||
|
||||
let! branchProtections =
|
||||
client.RepoListBranchProtection (u.Owner.LoginName, u.FullName)
|
||||
|> Async.AwaitTask
|
||||
|
||||
return
|
||||
|
||||
{
|
||||
@@ -194,6 +217,15 @@ type Repo =
|
||||
GitHubAddress = Uri m.RemoteAddress
|
||||
}
|
||||
)
|
||||
ProtectedBranches =
|
||||
branchProtections
|
||||
|> Seq.map (fun bp ->
|
||||
{
|
||||
BranchName = bp.BranchName
|
||||
BlockOnOutdatedBranch = bp.BlockOnOutdatedBranch
|
||||
}
|
||||
)
|
||||
|> Set.ofSeq
|
||||
}
|
||||
|> Some
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AssemblyInfo.fs" />
|
||||
<Compile Include="Map.fs" />
|
||||
<Compile Include="Async.fs" />
|
||||
<Compile Include="GiteaClient.fs" />
|
||||
<Compile Include="Domain.fs" />
|
||||
<Compile Include="SerialisedConfigSchema.fs" />
|
||||
|
@@ -232,11 +232,18 @@ module Gitea =
|
||||
}
|
||||
| AlignmentError.ConfigurationDiffers (desired, actual) ->
|
||||
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 ->
|
||||
async {
|
||||
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,
|
||||
r
|
||||
)
|
||||
@@ -474,6 +481,86 @@ module Gitea =
|
||||
async { logger.LogCritical ("Push mirror on {User}:{Repo} differs.", user, r) }
|
||||
else
|
||||
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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"protectedBranches": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"description": "Protected branch configuration",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SerialisedProtectedBranch"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -195,12 +205,28 @@
|
||||
"type": "object",
|
||||
"description": "Information about a repo that is to be created on Gitea without syncing from GitHub.",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"gitHubAddress"
|
||||
],
|
||||
"properties": {
|
||||
"gitHubAddress": {
|
||||
"type": [
|
||||
"null",
|
||||
"string"
|
||||
]
|
||||
"type": "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.">]
|
||||
type SerialisedPushMirror =
|
||||
{
|
||||
[<JsonProperty(Required = Required.Always)>]
|
||||
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>]
|
||||
[<Struct>]
|
||||
[<CLIMutable>]
|
||||
@@ -67,6 +80,9 @@ type internal SerialisedNativeRepo =
|
||||
[<JsonProperty(Required = Required.DisallowNull)>]
|
||||
[<Description "Configure a GitHub push mirror to sync this repo to">]
|
||||
Mirror : Nullable<SerialisedPushMirror>
|
||||
[<JsonProperty(Required = Required.Default)>]
|
||||
[<Description "Protected branch configuration">]
|
||||
ProtectedBranches : SerialisedProtectedBranch array
|
||||
}
|
||||
|
||||
[<Struct>]
|
||||
|
Reference in New Issue
Block a user