From e76fc16e38da5d756062e49797ebc7e93543c810 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 17 Aug 2026 02:42:29 -0400 Subject: [PATCH] fix(rules): keep the public toggle on repository visibility rules GitHub's repository policy API exposes a third visibility toggle, and dropping it on unmarshal silently revokes public visibility on any full-replace update built from a ruleset the SDK read back. Signed-off-by: Yordis Prieto --- github/github-accessors.go | 8 ++++++++ github/github-accessors_test.go | 8 ++++++++ github/rules.go | 1 + github/rules_test.go | 25 +++++++++++++++++++------ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index ac6b242f8a0..87769b53cfd 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -36886,6 +36886,14 @@ func (r *RepositoryVisibilityRuleParameters) GetPrivate() bool { return r.Private } +// GetPublic returns the Public field. +func (r *RepositoryVisibilityRuleParameters) GetPublic() bool { + if r == nil { + return false + } + return r.Public +} + // GetAffectedPackageName returns the AffectedPackageName field if it's non-nil, zero value otherwise. func (r *RepositoryVulnerabilityAlert) GetAffectedPackageName() string { if r == nil || r.AffectedPackageName == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index fdc048b3a0d..3a36dc98755 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -46172,6 +46172,14 @@ func TestRepositoryVisibilityRuleParameters_GetPrivate(tt *testing.T) { r.GetPrivate() } +func TestRepositoryVisibilityRuleParameters_GetPublic(tt *testing.T) { + tt.Parallel() + r := &RepositoryVisibilityRuleParameters{} + r.GetPublic() + r = nil + r.GetPublic() +} + func TestRepositoryVulnerabilityAlert_GetAffectedPackageName(tt *testing.T) { tt.Parallel() var zeroValue string diff --git a/github/rules.go b/github/rules.go index 93ccf95ad99..e6f48618a49 100644 --- a/github/rules.go +++ b/github/rules.go @@ -602,6 +602,7 @@ type SimplePatternRuleParameters struct { type RepositoryVisibilityRuleParameters struct { Internal bool `json:"internal"` Private bool `json:"private"` + Public bool `json:"public"` } // repositoryRulesetRuleWrapper is a helper type to marshal & unmarshal a ruleset rule. diff --git a/github/rules_test.go b/github/rules_test.go index cf46f60dd62..ff0c69efc2c 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -125,9 +125,9 @@ func TestRepositoryRulesetRules(t *testing.T) { RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, - RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false}, + RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false, Public: false}, }, - `[{"type":"creation"},{"type":"update"},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, + `[{"type":"creation"},{"type":"update"},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false,"public":false}}]`, }, { "all_rules_with_all_params", @@ -246,9 +246,9 @@ func TestRepositoryRulesetRules(t *testing.T) { RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, - RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false}, + RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false, Public: false}, }, - `[{"type":"creation"},{"type":"update","parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"do_not_enforce_on_create":true,"required_status_checks":[{"context":"test1","integration_id":1},{"context":"test2","integration_id":2}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"name":"cmp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"name":"caep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"name":"cep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"name":"bp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"name":"tp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"do_not_enforce_on_create":true,"workflows":[{"path":".github/workflows/test1.yaml","ref":"main","repository_id":1,"sha":"aaaa"},{"path":".github/workflows/test2.yaml","ref":"main","repository_id":2,"sha":"bbbb"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, + `[{"type":"creation"},{"type":"update","parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"do_not_enforce_on_create":true,"required_status_checks":[{"context":"test1","integration_id":1},{"context":"test2","integration_id":2}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"name":"cmp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"name":"caep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"name":"cep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"name":"bp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"name":"tp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"do_not_enforce_on_create":true,"workflows":[{"path":".github/workflows/test1.yaml","ref":"main","repository_id":1,"sha":"aaaa"},{"path":".github/workflows/test2.yaml","ref":"main","repository_id":2,"sha":"bbbb"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false,"public":false}}]`, }, } @@ -1069,11 +1069,12 @@ func TestRepositoryRule(t *testing.T) { &RepositoryRule{ Type: RulesetRuleTypeRepositoryVisibility, Parameters: &RepositoryVisibilityRuleParameters{ - Internal: false, + Internal: true, Private: false, + Public: true, }, }, - `{"type":"repository_visibility","parameters":{"internal":false,"private":false}}`, + `{"type":"repository_visibility","parameters":{"internal":true,"private":false,"public":true}}`, }, } @@ -1106,6 +1107,18 @@ func TestRepositoryRule(t *testing.T) { }, `{"type":"copilot_code_review","parameters":{"review_on_push":false,"review_draft_pull_requests":false}}`, }, + { + "repository_visibility", + &RepositoryRule{ + Type: RulesetRuleTypeRepositoryVisibility, + Parameters: &RepositoryVisibilityRuleParameters{ + Internal: true, + Private: false, + Public: true, + }, + }, + `{"type":"repository_visibility","parameters":{"internal":true,"private":false,"public":true}}`, + }, } t.Run("MarshalJSON", func(t *testing.T) {