From c72c03e64f31c268c9dd5858aca9f63ba6dd3aa4 Mon Sep 17 00:00:00 2001 From: Simon Nordberg Date: Wed, 14 Oct 2026 11:38:06 +0200 Subject: [PATCH] fix: use *string for clearable fields on update endpoints buildBody skips zero-value fields, which meant users could never clear a string field (description, website, body, etc.) via the CLI. Booleans and ints already used pointer types with Cobra's Changed() pattern. Extend the same pattern to strings: ~16 fields on update/PATCH endpoints changed from string to *string. The code generator now handles *string alongside *bool/*int. Create endpoints are unchanged (empty = not set). --- client/client_test.go | 21 ++++++++++++ cmd/gen/main.go | 14 ++++++++ internal/gen/issue.go | 18 +++++++++-- internal/gen/pr.go | 12 +++++-- internal/gen/protection.go | 12 +++++-- internal/gen/release.go | 12 +++++-- internal/gen/repo.go | 12 +++++-- internal/gen/webhook.go | 24 +++++++++++--- tools/issue.go | 30 ++++++++--------- tools/pr.go | 16 ++++----- tools/protection.go | 48 +++++++++++++-------------- tools/release.go | 18 +++++------ tools/repo.go | 66 +++++++++++++++++++------------------- tools/webhook.go | 42 ++++++++++++------------ 14 files changed, 220 insertions(+), 125 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 5caa66c..71b1356 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -568,6 +568,27 @@ func TestExecuteDELETEWithBody(t *testing.T) { } } +func TestBuildBodyOptionalStringSendsExplicitEmpty(t *testing.T) { + type args struct { + Owner string `json:"owner"` + Desc *string `json:"description"` + Site *string `json:"website"` + } + + empty := "" + body := buildBody(args{Owner: "acme", Desc: &empty, Site: nil}, map[string]bool{"Owner": true}) + m, ok := body.(map[string]any) + if !ok { + t.Fatalf("body type = %T, want map[string]any", body) + } + if _, ok := m["description"]; !ok { + t.Error("explicit empty *string should be included in body") + } + if _, ok := m["website"]; ok { + t.Error("nil *string should be excluded from body") + } +} + func TestBuildBodyCSVTag(t *testing.T) { type args struct { Owner string `json:"owner"` diff --git a/cmd/gen/main.go b/cmd/gen/main.go index 585ded5..bb9eb69 100644 --- a/cmd/gen/main.go +++ b/cmd/gen/main.go @@ -69,6 +69,7 @@ type fieldData struct { ContextInfer bool IsOptBool bool IsOptInt bool + IsOptString bool } func groupedTools() map[string][]tools.ToolDef { @@ -125,6 +126,7 @@ func generateGroup(name string, defs []tools.ToolDef) error { ContextInfer: hasRepo && (jsonName == "owner" || jsonName == "repo"), IsOptBool: isOptBool(f.Type), IsOptInt: isOptInt(f.Type), + IsOptString: isOptString(f.Type), }) } @@ -194,6 +196,10 @@ func isOptInt(t reflect.Type) bool { return t.Kind() == reflect.Ptr && (t.Elem().Kind() >= reflect.Int && t.Elem().Kind() <= reflect.Int64) } +func isOptString(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.String +} + func toExportedName(s string) string { parts := strings.FieldsFunc(s, func(r rune) bool { return r == '_' || r == '-' @@ -320,6 +326,8 @@ func {{.FuncName}}Cmd() *cobra.Command { var _{{.GoName}} bool {{- end}}{{- if .IsOptInt}} var _{{.GoName}} int +{{- end}}{{- if .IsOptString}} + var _{{.GoName}} string {{- end}}{{- end}} cmd := &cobra.Command{ Use: "{{.CLIName}}", @@ -333,6 +341,10 @@ func {{.FuncName}}Cmd() *cobra.Command { if cmd.Flags().Changed("{{.JSONName}}") { args.{{.GoName}} = &_{{.GoName}} } +{{- end}}{{- if .IsOptString}} + if cmd.Flags().Changed("{{.JSONName}}") { + args.{{.GoName}} = &_{{.GoName}} + } {{- end}}{{- end}} td := tools.ByName("{{.ToolName}}") return ExecuteAndPrint(cmd, td, &args) @@ -343,6 +355,8 @@ func {{.FuncName}}Cmd() *cobra.Command { cmd.Flags().BoolVar(&_{{.GoName}}, "{{.JSONName}}", false, ` + "`{{.Desc}}`" + `) {{- else if .IsOptInt}} cmd.Flags().IntVar(&_{{.GoName}}, "{{.JSONName}}", 0, ` + "`{{.Desc}}`" + `) +{{- else if .IsOptString}} + cmd.Flags().StringVar(&_{{.GoName}}, "{{.JSONName}}", "", ` + "`{{.Desc}}`" + `) {{- else}} cmd.Flags().{{.FlagType}}Var(&args.{{.GoName}}, "{{.JSONName}}", {{defaultVal .FlagType .Default}}, ` + "`{{.Desc}}`" + `) {{- end}} diff --git a/internal/gen/issue.go b/internal/gen/issue.go index 3ecf427..82325ac 100644 --- a/internal/gen/issue.go +++ b/internal/gen/issue.go @@ -111,10 +111,18 @@ func IssueCommentCmd() *cobra.Command { func IssueUpdateCmd() *cobra.Command { var args tools.UpdateIssueArgs + var _Body string + var _Assignee string cmd := &cobra.Command{ Use: "update", Short: `Update issue`, RunE: func(cmd *cobra.Command, _ []string) error { + if cmd.Flags().Changed("body") { + args.Body = &_Body + } + if cmd.Flags().Changed("assignee") { + args.Assignee = &_Assignee + } td := tools.ByName("update_issue") return ExecuteAndPrint(cmd, td, &args) }, @@ -124,8 +132,8 @@ func IssueUpdateCmd() *cobra.Command { cmd.Flags().IntVar(&args.Index, "index", 0, `Issue index`) _ = cmd.MarkFlagRequired("index") cmd.Flags().StringVar(&args.Title, "title", "", `Title`) - cmd.Flags().StringVar(&args.Body, "body", "", `Content body`) - cmd.Flags().StringVar(&args.Assignee, "assignee", "", `Assignee username (convenience for a single user; equivalent to a one-element 'assignees')`) + cmd.Flags().StringVar(&_Body, "body", "", `Content body`) + cmd.Flags().StringVar(&_Assignee, "assignee", "", `Assignee username (convenience for a single user; equivalent to a one-element 'assignees')`) cmd.Flags().StringVar(&args.Assignees, "assignees", "", `Assignee usernames (comma-separated). Overrides 'assignee' if both are set. Pass an empty string to clear all assignees.`) cmd.Flags().IntVar(&args.Milestone, "milestone", 0, `Milestone ID`) return cmd @@ -319,10 +327,14 @@ func IssueCreateLabelCmd() *cobra.Command { func IssueEditLabelCmd() *cobra.Command { var args tools.EditLabelArgs + var _Description string cmd := &cobra.Command{ Use: "edit-label", Short: `Edit a label on a repository or organization`, RunE: func(cmd *cobra.Command, _ []string) error { + if cmd.Flags().Changed("description") { + args.Description = &_Description + } td := tools.ByName("edit_label") return ExecuteAndPrint(cmd, td, &args) }, @@ -333,7 +345,7 @@ func IssueEditLabelCmd() *cobra.Command { cmd.Flags().StringVar(&args.Repo, "repo", "", `Repository name. Omit to edit an org-level label.`) cmd.Flags().StringVar(&args.Name, "name", "", `New label name`) cmd.Flags().StringVar(&args.Color, "color", "", `New label color hex code`) - cmd.Flags().StringVar(&args.Description, "description", "", `New label description`) + cmd.Flags().StringVar(&_Description, "description", "", `New label description`) cmd.Flags().BoolVar(&args.Exclusive, "exclusive", false, `Whether this label is exclusive`) return cmd } diff --git a/internal/gen/pr.go b/internal/gen/pr.go index 5ba7d5b..1448c05 100644 --- a/internal/gen/pr.go +++ b/internal/gen/pr.go @@ -96,10 +96,18 @@ func PRCreateCmd() *cobra.Command { func PRUpdateCmd() *cobra.Command { var args tools.UpdatePullRequestArgs + var _Body string + var _Assignee string cmd := &cobra.Command{ Use: "update", Short: `Update pull request`, RunE: func(cmd *cobra.Command, _ []string) error { + if cmd.Flags().Changed("body") { + args.Body = &_Body + } + if cmd.Flags().Changed("assignee") { + args.Assignee = &_Assignee + } td := tools.ByName("update_pull_request") return ExecuteAndPrint(cmd, td, &args) }, @@ -109,9 +117,9 @@ func PRUpdateCmd() *cobra.Command { cmd.Flags().IntVar(&args.Index, "index", 0, `PR index`) _ = cmd.MarkFlagRequired("index") cmd.Flags().StringVar(&args.Title, "title", "", `Title`) - cmd.Flags().StringVar(&args.Body, "body", "", `Content body`) + cmd.Flags().StringVar(&_Body, "body", "", `Content body`) cmd.Flags().StringVar(&args.Base, "base", "", `Base branch`) - cmd.Flags().StringVar(&args.Assignee, "assignee", "", `Assignee username`) + cmd.Flags().StringVar(&_Assignee, "assignee", "", `Assignee username`) cmd.Flags().IntVar(&args.Milestone, "milestone", 0, `Milestone ID`) return cmd } diff --git a/internal/gen/protection.go b/internal/gen/protection.go index f18423a..da8983a 100644 --- a/internal/gen/protection.go +++ b/internal/gen/protection.go @@ -154,6 +154,8 @@ func ProtectionUpdateCmd() *cobra.Command { var _BlockOnOutdatedBranch bool var _DismissStaleApprovals bool var _RequireSignedCommits bool + var _ProtectedFilePatterns string + var _UnprotectedFilePatterns string cmd := &cobra.Command{ Use: "update", Short: `Update a branch protection rule`, @@ -194,6 +196,12 @@ func ProtectionUpdateCmd() *cobra.Command { if cmd.Flags().Changed("require_signed_commits") { args.RequireSignedCommits = &_RequireSignedCommits } + if cmd.Flags().Changed("protected_file_patterns") { + args.ProtectedFilePatterns = &_ProtectedFilePatterns + } + if cmd.Flags().Changed("unprotected_file_patterns") { + args.UnprotectedFilePatterns = &_UnprotectedFilePatterns + } td := tools.ByName("update_branch_protection") return ExecuteAndPrint(cmd, td, &args) }, @@ -221,8 +229,8 @@ func ProtectionUpdateCmd() *cobra.Command { cmd.Flags().BoolVar(&_BlockOnOutdatedBranch, "block_on_outdated_branch", false, `Block merging when the head branch is outdated`) cmd.Flags().BoolVar(&_DismissStaleApprovals, "dismiss_stale_approvals", false, `Dismiss approvals when new commits are pushed`) cmd.Flags().BoolVar(&_RequireSignedCommits, "require_signed_commits", false, `Require commits to be signed`) - cmd.Flags().StringVar(&args.ProtectedFilePatterns, "protected_file_patterns", "", `Glob patterns of files that cannot be changed directly (semicolon-separated)`) - cmd.Flags().StringVar(&args.UnprotectedFilePatterns, "unprotected_file_patterns", "", `Glob patterns of files exempt from protection (semicolon-separated)`) + cmd.Flags().StringVar(&_ProtectedFilePatterns, "protected_file_patterns", "", `Glob patterns of files that cannot be changed directly (semicolon-separated)`) + cmd.Flags().StringVar(&_UnprotectedFilePatterns, "unprotected_file_patterns", "", `Glob patterns of files exempt from protection (semicolon-separated)`) return cmd } diff --git a/internal/gen/release.go b/internal/gen/release.go index ee29b40..cb2bb24 100644 --- a/internal/gen/release.go +++ b/internal/gen/release.go @@ -119,12 +119,20 @@ func ReleaseGetByTagCmd() *cobra.Command { func ReleaseUpdateCmd() *cobra.Command { var args tools.UpdateReleaseArgs + var _Title string + var _Note string var _Draft bool var _Prerelease bool cmd := &cobra.Command{ Use: "update", Short: `Update a release`, RunE: func(cmd *cobra.Command, _ []string) error { + if cmd.Flags().Changed("name") { + args.Title = &_Title + } + if cmd.Flags().Changed("body") { + args.Note = &_Note + } if cmd.Flags().Changed("draft") { args.Draft = &_Draft } @@ -141,8 +149,8 @@ func ReleaseUpdateCmd() *cobra.Command { _ = cmd.MarkFlagRequired("id") cmd.Flags().StringVar(&args.TagName, "tag_name", "", `Tag name for the release`) cmd.Flags().StringVar(&args.Target, "target_commitish", "", `Target branch or commit SHA`) - cmd.Flags().StringVar(&args.Title, "name", "", `Release title`) - cmd.Flags().StringVar(&args.Note, "body", "", `Release notes body`) + cmd.Flags().StringVar(&_Title, "name", "", `Release title`) + cmd.Flags().StringVar(&_Note, "body", "", `Release notes body`) cmd.Flags().BoolVar(&_Draft, "draft", false, `Mark as a draft release`) cmd.Flags().BoolVar(&_Prerelease, "prerelease", false, `Mark as a prerelease`) return cmd diff --git a/internal/gen/repo.go b/internal/gen/repo.go index 4bf5e95..a027ebb 100644 --- a/internal/gen/repo.go +++ b/internal/gen/repo.go @@ -75,6 +75,8 @@ func RepoListCmd() *cobra.Command { func RepoUpdateCmd() *cobra.Command { var args tools.UpdateRepoArgs + var _Description string + var _Website string var _Private bool var _Template bool var _Archived bool @@ -99,6 +101,12 @@ func RepoUpdateCmd() *cobra.Command { Use: "update", Short: `Update repository settings (common subset): name, description, website, default_branch, private, archived, has_issues, has_pull_requests, has_releases, has_packages, has_actions, allow_merge_commits, allow_rebase, allow_squash_merge, default_merge_style, default_delete_branch_after_merge. All fields optional; omitted fields are left unchanged. Accepts the full Forgejo EditRepoOption surface beyond the common subset.`, RunE: func(cmd *cobra.Command, _ []string) error { + if cmd.Flags().Changed("description") { + args.Description = &_Description + } + if cmd.Flags().Changed("website") { + args.Website = &_Website + } if cmd.Flags().Changed("private") { args.Private = &_Private } @@ -166,8 +174,8 @@ func RepoUpdateCmd() *cobra.Command { cmd.Flags().StringVar(&args.Owner, "owner", "", `Repository owner`) cmd.Flags().StringVar(&args.Repo, "repo", "", `Repository name`) cmd.Flags().StringVar(&args.Name, "name", "", `New repository name`) - cmd.Flags().StringVar(&args.Description, "description", "", `Description`) - cmd.Flags().StringVar(&args.Website, "website", "", `Website URL`) + cmd.Flags().StringVar(&_Description, "description", "", `Description`) + cmd.Flags().StringVar(&_Website, "website", "", `Website URL`) cmd.Flags().StringVar(&args.DefaultBranch, "default_branch", "", `Default branch`) cmd.Flags().StringVar(&args.DefaultMergeStyle, "default_merge_style", "", `Default merge style (merge, rebase, rebase-merge, squash, fast-forward-only)`) cmd.Flags().StringVar(&args.MirrorInterval, "mirror_interval", "", `Mirror sync interval (e.g. 8h, 0 to disable)`) diff --git a/internal/gen/webhook.go b/internal/gen/webhook.go index 3eb0894..c11abe6 100644 --- a/internal/gen/webhook.go +++ b/internal/gen/webhook.go @@ -94,6 +94,8 @@ func WebhookCreateCmd() *cobra.Command { func WebhookUpdateCmd() *cobra.Command { var args tools.UpdateHookArgs var _Active bool + var _BranchFilter string + var _AuthorizationHeader string cmd := &cobra.Command{ Use: "update", Short: `Update a repository webhook`, @@ -101,6 +103,12 @@ func WebhookUpdateCmd() *cobra.Command { if cmd.Flags().Changed("active") { args.Active = &_Active } + if cmd.Flags().Changed("branch_filter") { + args.BranchFilter = &_BranchFilter + } + if cmd.Flags().Changed("authorization_header") { + args.AuthorizationHeader = &_AuthorizationHeader + } td := tools.ByName("update_hook") return ExecuteAndPrint(cmd, td, &args) }, @@ -115,8 +123,8 @@ func WebhookUpdateCmd() *cobra.Command { cmd.Flags().StringVar(&args.Secret, "secret", "", `Secret used to sign the payload`) cmd.Flags().StringVar(&args.Events, "events", "", `Events that trigger the webhook (comma-separated, e.g. push,pull_request)`) cmd.Flags().BoolVar(&_Active, "active", false, `Whether the webhook is active`) - cmd.Flags().StringVar(&args.BranchFilter, "branch_filter", "", `Glob pattern restricting which branches trigger the webhook`) - cmd.Flags().StringVar(&args.AuthorizationHeader, "authorization_header", "", `Authorization header value sent with each delivery`) + cmd.Flags().StringVar(&_BranchFilter, "branch_filter", "", `Glob pattern restricting which branches trigger the webhook`) + cmd.Flags().StringVar(&_AuthorizationHeader, "authorization_header", "", `Authorization header value sent with each delivery`) return cmd } @@ -220,6 +228,8 @@ func WebhookCreateOrgCmd() *cobra.Command { func WebhookUpdateOrgCmd() *cobra.Command { var args tools.UpdateOrgHookArgs var _Active bool + var _BranchFilter string + var _AuthorizationHeader string cmd := &cobra.Command{ Use: "update-org", Short: `Update an organization webhook`, @@ -227,6 +237,12 @@ func WebhookUpdateOrgCmd() *cobra.Command { if cmd.Flags().Changed("active") { args.Active = &_Active } + if cmd.Flags().Changed("branch_filter") { + args.BranchFilter = &_BranchFilter + } + if cmd.Flags().Changed("authorization_header") { + args.AuthorizationHeader = &_AuthorizationHeader + } td := tools.ByName("update_org_hook") return ExecuteAndPrint(cmd, td, &args) }, @@ -241,8 +257,8 @@ func WebhookUpdateOrgCmd() *cobra.Command { cmd.Flags().StringVar(&args.Secret, "secret", "", `Secret used to sign the payload`) cmd.Flags().StringVar(&args.Events, "events", "", `Events that trigger the webhook (comma-separated, e.g. push,pull_request)`) cmd.Flags().BoolVar(&_Active, "active", false, `Whether the webhook is active`) - cmd.Flags().StringVar(&args.BranchFilter, "branch_filter", "", `Glob pattern restricting which branches trigger the webhook`) - cmd.Flags().StringVar(&args.AuthorizationHeader, "authorization_header", "", `Authorization header value sent with each delivery`) + cmd.Flags().StringVar(&_BranchFilter, "branch_filter", "", `Glob pattern restricting which branches trigger the webhook`) + cmd.Flags().StringVar(&_AuthorizationHeader, "authorization_header", "", `Authorization header value sent with each delivery`) return cmd } diff --git a/tools/issue.go b/tools/issue.go index c1e5abe..f0bc729 100644 --- a/tools/issue.go +++ b/tools/issue.go @@ -36,14 +36,14 @@ type CreateIssueCommentArgs struct { } type UpdateIssueArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - Index int `json:"index" required:"true" desc:"Issue index"` - Title string `json:"title" desc:"Title"` - Body string `json:"body" desc:"Content body"` - Assignee string `json:"assignee" desc:"Assignee username (convenience for a single user; equivalent to a one-element 'assignees')"` - Assignees string `json:"assignees" desc:"Assignee usernames (comma-separated). Overrides 'assignee' if both are set. Pass an empty string to clear all assignees." body:"csv"` - Milestone int `json:"milestone" desc:"Milestone ID"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + Index int `json:"index" required:"true" desc:"Issue index"` + Title string `json:"title" desc:"Title"` + Body *string `json:"body" desc:"Content body"` + Assignee *string `json:"assignee" desc:"Assignee username (convenience for a single user; equivalent to a one-element 'assignees')"` + Assignees string `json:"assignees" desc:"Assignee usernames (comma-separated). Overrides 'assignee' if both are set. Pass an empty string to clear all assignees." body:"csv"` + Milestone int `json:"milestone" desc:"Milestone ID"` } type AddIssueLabelsArgs struct { @@ -120,13 +120,13 @@ type CreateLabelArgs struct { } type EditLabelArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner or organization name"` - ID int `json:"id" required:"true" desc:"Label ID"` - Repo string `json:"repo" desc:"Repository name. Omit to edit an org-level label."` - Name string `json:"name" desc:"New label name"` - Color string `json:"color" desc:"New label color hex code"` - Description string `json:"description" desc:"New label description"` - Exclusive bool `json:"exclusive" desc:"Whether this label is exclusive"` + Owner string `json:"owner" required:"true" desc:"Repository owner or organization name"` + ID int `json:"id" required:"true" desc:"Label ID"` + Repo string `json:"repo" desc:"Repository name. Omit to edit an org-level label."` + Name string `json:"name" desc:"New label name"` + Color string `json:"color" desc:"New label color hex code"` + Description *string `json:"description" desc:"New label description"` + Exclusive bool `json:"exclusive" desc:"Whether this label is exclusive"` } type DeleteLabelArgs struct { diff --git a/tools/pr.go b/tools/pr.go index 6ce5755..c770fc8 100644 --- a/tools/pr.go +++ b/tools/pr.go @@ -31,14 +31,14 @@ type CreatePullRequestArgs struct { } type UpdatePullRequestArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - Index int `json:"index" required:"true" desc:"PR index"` - Title string `json:"title" desc:"Title"` - Body string `json:"body" desc:"Content body"` - Base string `json:"base" desc:"Base branch"` - Assignee string `json:"assignee" desc:"Assignee username"` - Milestone int `json:"milestone" desc:"Milestone ID"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + Index int `json:"index" required:"true" desc:"PR index"` + Title string `json:"title" desc:"Title"` + Body *string `json:"body" desc:"Content body"` + Base string `json:"base" desc:"Base branch"` + Assignee *string `json:"assignee" desc:"Assignee username"` + Milestone int `json:"milestone" desc:"Milestone ID"` } type ListPullReviewsArgs struct { diff --git a/tools/protection.go b/tools/protection.go index c216786..cff3b3b 100644 --- a/tools/protection.go +++ b/tools/protection.go @@ -39,30 +39,30 @@ type CreateBranchProtectionArgs struct { } type UpdateBranchProtectionArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - Name string `json:"name" required:"true" desc:"Branch protection rule name (glob pattern)"` - EnablePush *bool `json:"enable_push" desc:"Allow whitelisted users/teams to push directly"` - EnablePushWhitelist *bool `json:"enable_push_whitelist" desc:"Restrict direct pushes to the push whitelist"` - PushWhitelistUsernames string `json:"push_whitelist_usernames" desc:"Usernames allowed to push directly (comma-separated)" body:"csv"` - PushWhitelistTeams string `json:"push_whitelist_teams" desc:"Teams allowed to push directly (comma-separated)" body:"csv"` - PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys" desc:"Allow deploy keys to push directly"` - EnableMergeWhitelist *bool `json:"enable_merge_whitelist" desc:"Restrict merging to the merge whitelist"` - MergeWhitelistUsernames string `json:"merge_whitelist_usernames" desc:"Usernames allowed to merge (comma-separated)" body:"csv"` - MergeWhitelistTeams string `json:"merge_whitelist_teams" desc:"Teams allowed to merge (comma-separated)" body:"csv"` - EnableStatusCheck *bool `json:"enable_status_check" desc:"Require status checks to pass before merging"` - StatusCheckContexts string `json:"status_check_contexts" desc:"Required status check contexts (comma-separated)" body:"csv"` - RequiredApprovals *int `json:"required_approvals" desc:"Minimum number of approving reviews required"` - EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist" desc:"Restrict qualifying approvals to the approvals whitelist"` - ApprovalsWhitelistUsernames string `json:"approvals_whitelist_username" desc:"Usernames whose approvals count (comma-separated)" body:"csv"` - ApprovalsWhitelistTeams string `json:"approvals_whitelist_teams" desc:"Teams whose approvals count (comma-separated)" body:"csv"` - BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews" desc:"Block merging when there are pending rejected reviews"` - BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests" desc:"Block merging when there are pending official review requests"` - BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch" desc:"Block merging when the head branch is outdated"` - DismissStaleApprovals *bool `json:"dismiss_stale_approvals" desc:"Dismiss approvals when new commits are pushed"` - RequireSignedCommits *bool `json:"require_signed_commits" desc:"Require commits to be signed"` - ProtectedFilePatterns string `json:"protected_file_patterns" desc:"Glob patterns of files that cannot be changed directly (semicolon-separated)"` - UnprotectedFilePatterns string `json:"unprotected_file_patterns" desc:"Glob patterns of files exempt from protection (semicolon-separated)"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + Name string `json:"name" required:"true" desc:"Branch protection rule name (glob pattern)"` + EnablePush *bool `json:"enable_push" desc:"Allow whitelisted users/teams to push directly"` + EnablePushWhitelist *bool `json:"enable_push_whitelist" desc:"Restrict direct pushes to the push whitelist"` + PushWhitelistUsernames string `json:"push_whitelist_usernames" desc:"Usernames allowed to push directly (comma-separated)" body:"csv"` + PushWhitelistTeams string `json:"push_whitelist_teams" desc:"Teams allowed to push directly (comma-separated)" body:"csv"` + PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys" desc:"Allow deploy keys to push directly"` + EnableMergeWhitelist *bool `json:"enable_merge_whitelist" desc:"Restrict merging to the merge whitelist"` + MergeWhitelistUsernames string `json:"merge_whitelist_usernames" desc:"Usernames allowed to merge (comma-separated)" body:"csv"` + MergeWhitelistTeams string `json:"merge_whitelist_teams" desc:"Teams allowed to merge (comma-separated)" body:"csv"` + EnableStatusCheck *bool `json:"enable_status_check" desc:"Require status checks to pass before merging"` + StatusCheckContexts string `json:"status_check_contexts" desc:"Required status check contexts (comma-separated)" body:"csv"` + RequiredApprovals *int `json:"required_approvals" desc:"Minimum number of approving reviews required"` + EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist" desc:"Restrict qualifying approvals to the approvals whitelist"` + ApprovalsWhitelistUsernames string `json:"approvals_whitelist_username" desc:"Usernames whose approvals count (comma-separated)" body:"csv"` + ApprovalsWhitelistTeams string `json:"approvals_whitelist_teams" desc:"Teams whose approvals count (comma-separated)" body:"csv"` + BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews" desc:"Block merging when there are pending rejected reviews"` + BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests" desc:"Block merging when there are pending official review requests"` + BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch" desc:"Block merging when the head branch is outdated"` + DismissStaleApprovals *bool `json:"dismiss_stale_approvals" desc:"Dismiss approvals when new commits are pushed"` + RequireSignedCommits *bool `json:"require_signed_commits" desc:"Require commits to be signed"` + ProtectedFilePatterns *string `json:"protected_file_patterns" desc:"Glob patterns of files that cannot be changed directly (semicolon-separated)"` + UnprotectedFilePatterns *string `json:"unprotected_file_patterns" desc:"Glob patterns of files exempt from protection (semicolon-separated)"` } type DeleteBranchProtectionArgs struct { diff --git a/tools/release.go b/tools/release.go index e780bcd..7135b11 100644 --- a/tools/release.go +++ b/tools/release.go @@ -36,15 +36,15 @@ type GetReleaseByTagArgs struct { } type UpdateReleaseArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - ID int `json:"id" required:"true" desc:"Release ID"` - TagName string `json:"tag_name" desc:"Tag name for the release"` - Target string `json:"target_commitish" desc:"Target branch or commit SHA"` - Title string `json:"name" desc:"Release title"` - Note string `json:"body" desc:"Release notes body"` - Draft *bool `json:"draft" desc:"Mark as a draft release"` - Prerelease *bool `json:"prerelease" desc:"Mark as a prerelease"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + ID int `json:"id" required:"true" desc:"Release ID"` + TagName string `json:"tag_name" desc:"Tag name for the release"` + Target string `json:"target_commitish" desc:"Target branch or commit SHA"` + Title *string `json:"name" desc:"Release title"` + Note *string `json:"body" desc:"Release notes body"` + Draft *bool `json:"draft" desc:"Mark as a draft release"` + Prerelease *bool `json:"prerelease" desc:"Mark as a prerelease"` } type DeleteReleaseArgs struct { diff --git a/tools/repo.go b/tools/repo.go index eace9ab..6796259 100644 --- a/tools/repo.go +++ b/tools/repo.go @@ -29,39 +29,39 @@ type CreateRepoArgs struct { const updateRepoDesc = `Update repository settings (common subset): name, description, website, default_branch, private, archived, has_issues, has_pull_requests, has_releases, has_packages, has_actions, allow_merge_commits, allow_rebase, allow_squash_merge, default_merge_style, default_delete_branch_after_merge. All fields optional; omitted fields are left unchanged. Accepts the full Forgejo EditRepoOption surface beyond the common subset.` type UpdateRepoArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - Name string `json:"name" desc:"New repository name"` - Description string `json:"description" desc:"Description"` - Website string `json:"website" desc:"Website URL"` - DefaultBranch string `json:"default_branch" desc:"Default branch"` - DefaultMergeStyle string `json:"default_merge_style" desc:"Default merge style (merge, rebase, rebase-merge, squash, fast-forward-only)"` - MirrorInterval string `json:"mirror_interval" desc:"Mirror sync interval (e.g. 8h, 0 to disable)"` - ExternalTrackerURL string `json:"external_tracker_url" desc:"External issue tracker URL" body:"nest" nest:"external_tracker"` - ExternalTrackerFormat string `json:"external_tracker_format" desc:"External issue tracker URL format" body:"nest" nest:"external_tracker"` - ExternalTrackerStyle string `json:"external_tracker_style" desc:"External issue tracker numbering style" body:"nest" nest:"external_tracker"` - ExternalTrackerRegexpPattern string `json:"external_tracker_regexp_pattern" desc:"External issue tracker regexp pattern" body:"nest" nest:"external_tracker"` - ExternalWikiURL string `json:"external_wiki_url" desc:"External wiki URL" body:"nest" nest:"external_wiki"` - Private *bool `json:"private" desc:"Private repo"` - Template *bool `json:"template" desc:"Template repo"` - Archived *bool `json:"archived" desc:"Archived (read-only)"` - HasIssues *bool `json:"has_issues" desc:"Enable issue tracker"` - HasWiki *bool `json:"has_wiki" desc:"Enable wiki"` - HasPullRequests *bool `json:"has_pull_requests" desc:"Enable pull requests"` - HasProjects *bool `json:"has_projects" desc:"Enable projects"` - HasReleases *bool `json:"has_releases" desc:"Enable releases"` - HasPackages *bool `json:"has_packages" desc:"Enable package registry"` - HasActions *bool `json:"has_actions" desc:"Enable Actions CI"` - IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts" desc:"Ignore whitespace-only conflicts when merging"` - AllowMergeCommits *bool `json:"allow_merge_commits" desc:"Allow merge commits"` - AllowRebase *bool `json:"allow_rebase" desc:"Allow rebase merging"` - AllowRebaseExplicit *bool `json:"allow_rebase_explicit" desc:"Allow rebase with explicit merge commit"` - AllowRebaseUpdate *bool `json:"allow_rebase_update" desc:"Allow rebase of PR head branch when behind base"` - AllowSquashMerge *bool `json:"allow_squash_merge" desc:"Allow squash merging"` - AllowFastForwardOnlyMerge *bool `json:"allow_fast_forward_only_merge" desc:"Allow fast-forward-only merging"` - DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge" desc:"Delete head branch by default after merge"` - DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit" desc:"Allow maintainer edits on PRs by default"` - EnablePrune *bool `json:"enable_prune" desc:"Enable automatic pruning of obsolete mirror branches"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + Name string `json:"name" desc:"New repository name"` + Description *string `json:"description" desc:"Description"` + Website *string `json:"website" desc:"Website URL"` + DefaultBranch string `json:"default_branch" desc:"Default branch"` + DefaultMergeStyle string `json:"default_merge_style" desc:"Default merge style (merge, rebase, rebase-merge, squash, fast-forward-only)"` + MirrorInterval string `json:"mirror_interval" desc:"Mirror sync interval (e.g. 8h, 0 to disable)"` + ExternalTrackerURL string `json:"external_tracker_url" desc:"External issue tracker URL" body:"nest" nest:"external_tracker"` + ExternalTrackerFormat string `json:"external_tracker_format" desc:"External issue tracker URL format" body:"nest" nest:"external_tracker"` + ExternalTrackerStyle string `json:"external_tracker_style" desc:"External issue tracker numbering style" body:"nest" nest:"external_tracker"` + ExternalTrackerRegexpPattern string `json:"external_tracker_regexp_pattern" desc:"External issue tracker regexp pattern" body:"nest" nest:"external_tracker"` + ExternalWikiURL string `json:"external_wiki_url" desc:"External wiki URL" body:"nest" nest:"external_wiki"` + Private *bool `json:"private" desc:"Private repo"` + Template *bool `json:"template" desc:"Template repo"` + Archived *bool `json:"archived" desc:"Archived (read-only)"` + HasIssues *bool `json:"has_issues" desc:"Enable issue tracker"` + HasWiki *bool `json:"has_wiki" desc:"Enable wiki"` + HasPullRequests *bool `json:"has_pull_requests" desc:"Enable pull requests"` + HasProjects *bool `json:"has_projects" desc:"Enable projects"` + HasReleases *bool `json:"has_releases" desc:"Enable releases"` + HasPackages *bool `json:"has_packages" desc:"Enable package registry"` + HasActions *bool `json:"has_actions" desc:"Enable Actions CI"` + IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts" desc:"Ignore whitespace-only conflicts when merging"` + AllowMergeCommits *bool `json:"allow_merge_commits" desc:"Allow merge commits"` + AllowRebase *bool `json:"allow_rebase" desc:"Allow rebase merging"` + AllowRebaseExplicit *bool `json:"allow_rebase_explicit" desc:"Allow rebase with explicit merge commit"` + AllowRebaseUpdate *bool `json:"allow_rebase_update" desc:"Allow rebase of PR head branch when behind base"` + AllowSquashMerge *bool `json:"allow_squash_merge" desc:"Allow squash merging"` + AllowFastForwardOnlyMerge *bool `json:"allow_fast_forward_only_merge" desc:"Allow fast-forward-only merging"` + DefaultDeleteBranchAfterMerge *bool `json:"default_delete_branch_after_merge" desc:"Delete head branch by default after merge"` + DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit" desc:"Allow maintainer edits on PRs by default"` + EnablePrune *bool `json:"enable_prune" desc:"Enable automatic pruning of obsolete mirror branches"` } type ListMyReposArgs struct { diff --git a/tools/webhook.go b/tools/webhook.go index 3c032dd..c8bfd7d 100644 --- a/tools/webhook.go +++ b/tools/webhook.go @@ -14,17 +14,17 @@ type CreateHookArgs struct { } type UpdateHookArgs struct { - Owner string `json:"owner" required:"true" desc:"Repository owner"` - Repo string `json:"repo" required:"true" desc:"Repository name"` - ID int `json:"id" required:"true" desc:"Webhook ID"` - Type string `json:"type" desc:"Webhook type (e.g. gitea, slack, discord)"` - URL string `json:"url" desc:"Target URL the webhook payload is delivered to" body:"nest" nest:"config"` - ContentType string `json:"content_type" desc:"Payload content type (json or form)" body:"nest" nest:"config"` - Secret string `json:"secret" desc:"Secret used to sign the payload" body:"nest" nest:"config"` - Events string `json:"events" desc:"Events that trigger the webhook (comma-separated, e.g. push,pull_request)" body:"csv"` - Active *bool `json:"active" desc:"Whether the webhook is active"` - BranchFilter string `json:"branch_filter" desc:"Glob pattern restricting which branches trigger the webhook"` - AuthorizationHeader string `json:"authorization_header" desc:"Authorization header value sent with each delivery"` + Owner string `json:"owner" required:"true" desc:"Repository owner"` + Repo string `json:"repo" required:"true" desc:"Repository name"` + ID int `json:"id" required:"true" desc:"Webhook ID"` + Type string `json:"type" desc:"Webhook type (e.g. gitea, slack, discord)"` + URL string `json:"url" desc:"Target URL the webhook payload is delivered to" body:"nest" nest:"config"` + ContentType string `json:"content_type" desc:"Payload content type (json or form)" body:"nest" nest:"config"` + Secret string `json:"secret" desc:"Secret used to sign the payload" body:"nest" nest:"config"` + Events string `json:"events" desc:"Events that trigger the webhook (comma-separated, e.g. push,pull_request)" body:"csv"` + Active *bool `json:"active" desc:"Whether the webhook is active"` + BranchFilter *string `json:"branch_filter" desc:"Glob pattern restricting which branches trigger the webhook"` + AuthorizationHeader *string `json:"authorization_header" desc:"Authorization header value sent with each delivery"` } type ListHooksArgs struct { @@ -76,16 +76,16 @@ type CreateOrgHookArgs struct { } type UpdateOrgHookArgs struct { - Owner string `json:"owner" required:"true" desc:"Organization name"` - ID int `json:"id" required:"true" desc:"Webhook ID"` - Type string `json:"type" desc:"Webhook type (e.g. gitea, slack, discord)"` - URL string `json:"url" desc:"Target URL the webhook payload is delivered to" body:"nest" nest:"config"` - ContentType string `json:"content_type" desc:"Payload content type (json or form)" body:"nest" nest:"config"` - Secret string `json:"secret" desc:"Secret used to sign the payload" body:"nest" nest:"config"` - Events string `json:"events" desc:"Events that trigger the webhook (comma-separated, e.g. push,pull_request)" body:"csv"` - Active *bool `json:"active" desc:"Whether the webhook is active"` - BranchFilter string `json:"branch_filter" desc:"Glob pattern restricting which branches trigger the webhook"` - AuthorizationHeader string `json:"authorization_header" desc:"Authorization header value sent with each delivery"` + Owner string `json:"owner" required:"true" desc:"Organization name"` + ID int `json:"id" required:"true" desc:"Webhook ID"` + Type string `json:"type" desc:"Webhook type (e.g. gitea, slack, discord)"` + URL string `json:"url" desc:"Target URL the webhook payload is delivered to" body:"nest" nest:"config"` + ContentType string `json:"content_type" desc:"Payload content type (json or form)" body:"nest" nest:"config"` + Secret string `json:"secret" desc:"Secret used to sign the payload" body:"nest" nest:"config"` + Events string `json:"events" desc:"Events that trigger the webhook (comma-separated, e.g. push,pull_request)" body:"csv"` + Active *bool `json:"active" desc:"Whether the webhook is active"` + BranchFilter *string `json:"branch_filter" desc:"Glob pattern restricting which branches trigger the webhook"` + AuthorizationHeader *string `json:"authorization_header" desc:"Authorization header value sent with each delivery"` } type DeleteOrgHookArgs struct {