diff --git a/github/enterprise_rules.go b/github/enterprise_rules.go new file mode 100644 index 00000000000..29e3e4a568f --- /dev/null +++ b/github/enterprise_rules.go @@ -0,0 +1,118 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" +) + +// CreateEnterpriseRuleset creates a ruleset for the specified enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/rules#create-an-enterprise-repository-ruleset +// +//meta:operation POST /enterprises/{enterprise}/rulesets +func (s *EnterpriseService) CreateEnterpriseRuleset(ctx context.Context, enterprise string, ruleset Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("enterprises/%v/rulesets", enterprise) + + req, err := s.client.NewRequest("POST", u, ruleset) + if err != nil { + return nil, nil, err + } + + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, nil +} + +// GetEnterpriseRuleset gets a ruleset from the specified enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/rules#get-an-enterprise-repository-ruleset +// +//meta:operation GET /enterprises/{enterprise}/rulesets/{ruleset_id} +func (s *EnterpriseService) GetEnterpriseRuleset(ctx context.Context, enterprise string, rulesetID int64) (*Ruleset, *Response, error) { + u := fmt.Sprintf("enterprises/%v/rulesets/%v", enterprise, rulesetID) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var ruleset *Ruleset + resp, err := s.client.Do(ctx, req, &ruleset) + if err != nil { + return nil, resp, err + } + + return ruleset, resp, nil +} + +// UpdateEnterpriseRuleset updates a ruleset from the specified enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/rules#update-an-enterprise-repository-ruleset +// +//meta:operation PUT /enterprises/{enterprise}/rulesets/{ruleset_id} +func (s *EnterpriseService) UpdateEnterpriseRuleset(ctx context.Context, enterprise string, rulesetID int64, ruleset Ruleset) (*Ruleset, *Response, error) { + u := fmt.Sprintf("enterprises/%v/rulesets/%v", enterprise, rulesetID) + + req, err := s.client.NewRequest("PUT", u, ruleset) + if err != nil { + return nil, nil, err + } + + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, nil +} + +// UpdateEnterpriseRulesetClearBypassActor clears the ruleset bypass actors for a ruleset for the specified repository. +// +// This function is necessary as the UpdateEnterpriseRuleset function does not marshal ByPassActor if passed as an empty array. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/rules#update-an-enterprise-repository-ruleset +// +//meta:operation PUT /enterprises/{enterprise}/rulesets/{ruleset_id} +func (s *EnterpriseService) UpdateEnterpriseRulesetClearBypassActor(ctx context.Context, enterprise string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("enterprises/%v/rulesets/%v", enterprise, rulesetID) + + rsClearBypassActor := rulesetClearBypassActors{} + + req, err := s.client.NewRequest("PUT", u, rsClearBypassActor) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil +} + +// DeleteEnterpriseRuleset deletes a ruleset from the specified enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/rules#delete-an-enterprise-repository-ruleset +// +//meta:operation DELETE /enterprises/{enterprise}/rulesets/{ruleset_id} +func (s *EnterpriseService) DeleteEnterpriseRuleset(ctx context.Context, enterprise string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("enterprises/%v/rulesets/%v", enterprise, rulesetID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/github/enterprise_rules_test.go b/github/enterprise_rules_test.go new file mode 100644 index 00000000000..574b8396591 --- /dev/null +++ b/github/enterprise_rules_test.go @@ -0,0 +1,1852 @@ +// Copyright 2025 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestEnterpriseService_CreateEnterpriseRuleset_OrgNameRepoName(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{ + "id": 21, + "name": "ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_actors": [ + { + "actor_id": 234, + "actor_type": "Team" + } + ], + "conditions": { + "organization_name": { + "include": [ + "important_organization", + "another_important_organization" + ], + "exclude": [ + "unimportant_organization" + ] + }, + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": true + } + }, + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "required_deployments", + "parameters": { + "required_deployment_environments": ["test"] + } + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["rebase","squash"], + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + { + "context": "test", + "integration_id": 1 + } + ], + "strict_required_status_checks_policy": true + } + }, + { + "type": "non_fast_forward" + }, + { + "type": "commit_message_pattern", + "parameters": { + "name": "avoid test commits", + "negate": true, + "operator": "starts_with", + "pattern": "[test]" + } + }, + { + "type": "commit_author_email_pattern", + "parameters": { + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "committer_email_pattern", + "parameters": { + "name": "avoid commit emails", + "negate": true, + "operator": "ends_with", + "pattern": "abc" + } + }, + { + "type": "branch_name_pattern", + "parameters": { + "name": "avoid branch names", + "negate": true, + "operator": "regex", + "pattern": "github$" + } + }, + { + "type": "tag_name_pattern", + "parameters": { + "name": "avoid tag names", + "negate": true, + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "code_scanning", + "parameters": { + "code_scanning_tools": [ + { + "tool": "CodeQL", + "security_alerts_threshold": "high_or_higher", + "alerts_threshold": "errors" + } + ] + } + } + ] + }`) + }) + + ctx := context.Background() + ruleset, _, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{ + Name: "ruleset", + Target: Ptr("branch"), + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + }) + if err != nil { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(21)), + Name: "ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + } + if !cmp.Equal(ruleset, want) { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned %+v, want %+v", ruleset, want) + } + + const methodName = "CreateEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_CreateEnterpriseRuleset_OrgNameRepoProperty(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{ + "id": 21, + "name": "ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_actors": [ + { + "actor_id": 234, + "actor_type": "Team" + } + ], + "conditions": { + "organization_name": { + "include": [ + "important_organization", + "another_important_organization" + ], + "exclude": [ + "unimportant_organization" + ] + }, + "repository_property": { + "include": [ + { + "name": "testIncludeProp", + "source": "custom", + "property_values": [ + "true" + ] + } + ], + "exclude": [ + { + "name": "testExcludeProp", + "property_values": [ + "false" + ] + } + ] + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": true + } + }, + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "required_deployments", + "parameters": { + "required_deployment_environments": ["test"] + } + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["rebase","squash"], + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + { + "context": "test", + "integration_id": 1 + } + ], + "strict_required_status_checks_policy": true + } + }, + { + "type": "non_fast_forward" + }, + { + "type": "commit_message_pattern", + "parameters": { + "name": "avoid test commits", + "negate": true, + "operator": "starts_with", + "pattern": "[test]" + } + }, + { + "type": "commit_author_email_pattern", + "parameters": { + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "committer_email_pattern", + "parameters": { + "name": "avoid commit emails", + "negate": true, + "operator": "ends_with", + "pattern": "abc" + } + }, + { + "type": "branch_name_pattern", + "parameters": { + "name": "avoid branch names", + "negate": true, + "operator": "regex", + "pattern": "github$" + } + }, + { + "type": "tag_name_pattern", + "parameters": { + "name": "avoid tag names", + "negate": true, + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "code_scanning", + "parameters": { + "code_scanning_tools": [ + { + "tool": "CodeQL", + "security_alerts_threshold": "high_or_higher", + "alerts_threshold": "errors" + } + ] + } + } + ] + }`) + }) + + ctx := context.Background() + ruleset, _, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{ + Name: "ruleset", + Target: Ptr("branch"), + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryProperty: &RulesetRepositoryPropertyConditionParameters{ + Include: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testIncludeProp", + Source: Ptr("custom"), + Values: []string{"true"}, + }, + }, + Exclude: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testExcludeProp", + Values: []string{"false"}, + }, + }, + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + }) + if err != nil { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(21)), + Name: "ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryProperty: &RulesetRepositoryPropertyConditionParameters{ + Include: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testIncludeProp", + Source: Ptr("custom"), + Values: []string{"true"}, + }, + }, + Exclude: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testExcludeProp", + Values: []string{"false"}, + }, + }, + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + } + if !cmp.Equal(ruleset, want) { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned %+v, want %+v", ruleset, want) + } + + const methodName = "CreateEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_CreateEnterpriseRuleset_OrgIdRepoName(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{ + "id": 21, + "name": "ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_actors": [ + { + "actor_id": 234, + "actor_type": "Team" + } + ], + "conditions": { + "organization_id": { + "organization_ids": [1001, 1002] + }, + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": true + } + }, + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "required_deployments", + "parameters": { + "required_deployment_environments": ["test"] + } + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["rebase","squash"], + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + { + "context": "test", + "integration_id": 1 + } + ], + "strict_required_status_checks_policy": true + } + }, + { + "type": "non_fast_forward" + }, + { + "type": "commit_message_pattern", + "parameters": { + "name": "avoid test commits", + "negate": true, + "operator": "starts_with", + "pattern": "[test]" + } + }, + { + "type": "commit_author_email_pattern", + "parameters": { + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "committer_email_pattern", + "parameters": { + "name": "avoid commit emails", + "negate": true, + "operator": "ends_with", + "pattern": "abc" + } + }, + { + "type": "branch_name_pattern", + "parameters": { + "name": "avoid branch names", + "negate": true, + "operator": "regex", + "pattern": "github$" + } + }, + { + "type": "tag_name_pattern", + "parameters": { + "name": "avoid tag names", + "negate": true, + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "code_scanning", + "parameters": { + "code_scanning_tools": [ + { + "tool": "CodeQL", + "security_alerts_threshold": "high_or_higher", + "alerts_threshold": "errors" + } + ] + } + } + ] + }`) + }) + + ctx := context.Background() + ruleset, _, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{ + Name: "ruleset", + Target: Ptr("branch"), + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationID: &RulesetOrganizationIDsConditionParameters{ + OrganizationIDs: []int64{1001, 1002}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + }) + if err != nil { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(21)), + Name: "ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationID: &RulesetOrganizationIDsConditionParameters{ + OrganizationIDs: []int64{1001, 1002}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + } + if !cmp.Equal(ruleset, want) { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned %+v, want %+v", ruleset, want) + } + + const methodName = "CreateEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_CreateEnterpriseRuleset_OrgIdRepoProperty(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{ + "id": 21, + "name": "ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_actors": [ + { + "actor_id": 234, + "actor_type": "Team" + } + ], + "conditions": { + "organization_id": { + "organization_ids": [1001, 1002] + }, + "repository_property": { + "include": [ + { + "name": "testIncludeProp", + "source": "custom", + "property_values": [ + "true" + ] + } + ], + "exclude": [ + { + "name": "testExcludeProp", + "property_values": [ + "false" + ] + } + ] + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + }, + { + "type": "update", + "parameters": { + "update_allows_fetch_and_merge": true + } + }, + { + "type": "deletion" + }, + { + "type": "required_linear_history" + }, + { + "type": "required_deployments", + "parameters": { + "required_deployment_environments": ["test"] + } + }, + { + "type": "required_signatures" + }, + { + "type": "pull_request", + "parameters": { + "allowed_merge_methods": ["rebase","squash"], + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": true, + "require_last_push_approval": true, + "required_approving_review_count": 1, + "required_review_thread_resolution": true + } + }, + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + { + "context": "test", + "integration_id": 1 + } + ], + "strict_required_status_checks_policy": true + } + }, + { + "type": "non_fast_forward" + }, + { + "type": "commit_message_pattern", + "parameters": { + "name": "avoid test commits", + "negate": true, + "operator": "starts_with", + "pattern": "[test]" + } + }, + { + "type": "commit_author_email_pattern", + "parameters": { + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "committer_email_pattern", + "parameters": { + "name": "avoid commit emails", + "negate": true, + "operator": "ends_with", + "pattern": "abc" + } + }, + { + "type": "branch_name_pattern", + "parameters": { + "name": "avoid branch names", + "negate": true, + "operator": "regex", + "pattern": "github$" + } + }, + { + "type": "tag_name_pattern", + "parameters": { + "name": "avoid tag names", + "negate": true, + "operator": "contains", + "pattern": "github" + } + }, + { + "type": "code_scanning", + "parameters": { + "code_scanning_tools": [ + { + "tool": "CodeQL", + "security_alerts_threshold": "high_or_higher", + "alerts_threshold": "errors" + } + ] + } + } + ] + }`) + }) + + ctx := context.Background() + ruleset, _, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{ + Name: "ruleset", + Target: Ptr("branch"), + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationID: &RulesetOrganizationIDsConditionParameters{ + OrganizationIDs: []int64{1001, 1002}, + }, + RepositoryProperty: &RulesetRepositoryPropertyConditionParameters{ + Include: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testIncludeProp", + Source: Ptr("custom"), + Values: []string{"true"}, + }, + }, + Exclude: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testExcludeProp", + Values: []string{"false"}, + }, + }, + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + }) + if err != nil { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(21)), + Name: "ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + BypassActors: []*BypassActor{ + { + ActorID: Ptr(int64(234)), + ActorType: Ptr("Team"), + }, + }, + Conditions: &RulesetConditions{ + OrganizationID: &RulesetOrganizationIDsConditionParameters{ + OrganizationIDs: []int64{1001, 1002}, + }, + RepositoryProperty: &RulesetRepositoryPropertyConditionParameters{ + Include: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testIncludeProp", + Source: Ptr("custom"), + Values: []string{"true"}, + }, + }, + Exclude: []RulesetRepositoryPropertyTargetParameters{ + { + Name: "testExcludeProp", + Values: []string{"false"}, + }, + }, + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + NewUpdateRule(&UpdateAllowsFetchAndMergeRuleParameters{ + UpdateAllowsFetchAndMerge: true, + }), + NewDeletionRule(), + NewRequiredLinearHistoryRule(), + NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{ + RequiredDeploymentEnvironments: []string{"test"}, + }), + NewRequiredSignaturesRule(), + NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, + RequireCodeOwnerReview: true, + RequireLastPushApproval: true, + RequiredApprovingReviewCount: 1, + RequiredReviewThreadResolution: true, + }), + NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ + RequiredStatusChecks: []RuleRequiredStatusChecks{ + { + Context: "test", + IntegrationID: Ptr(int64(1)), + }, + }, + StrictRequiredStatusChecksPolicy: true, + }), + NewNonFastForwardRule(), + NewCommitMessagePatternRule(&RulePatternParameters{ + Name: Ptr("avoid test commits"), + Negate: Ptr(true), + Operator: "starts_with", + Pattern: "[test]", + }), + NewCommitAuthorEmailPatternRule(&RulePatternParameters{ + Operator: "contains", + Pattern: "github", + }), + NewCommitterEmailPatternRule(&RulePatternParameters{ + Name: Ptr("avoid commit emails"), + Negate: Ptr(true), + Operator: "ends_with", + Pattern: "abc", + }), + NewBranchNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid branch names"), + Negate: Ptr(true), + Operator: "regex", + Pattern: "github$", + }), + NewTagNamePatternRule(&RulePatternParameters{ + Name: Ptr("avoid tag names"), + Negate: Ptr(true), + Operator: "contains", + Pattern: "github", + }), + NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{ + RequiredCodeScanningTools: []*RuleRequiredCodeScanningTool{ + { + Tool: "CodeQL", + SecurityAlertsThreshold: "high_or_higher", + AlertsThreshold: "errors", + }, + }, + }), + }, + } + if !cmp.Equal(ruleset, want) { + t.Errorf("Enterprise.CreateEnterpriseRuleset returned %+v, want %+v", ruleset, want) + } + + const methodName = "CreateEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.CreateEnterpriseRuleset(ctx, "e", Ruleset{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_GetEnterpriseRuleset(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets/26110", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "id": 26110, + "name": "test ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_mode": "none", + "node_id": "nid", + "_links": { + "self": { + "href": "https://api.github.com/enterprises/e/rulesets/26110" + } + }, + "conditions": { + "organization_name": { + "include": [ + "important_organization", + "another_important_organization" + ], + "exclude": [ + "unimportant_organization" + ] + }, + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + } + ] + }`) + }) + + ctx := context.Background() + rulesets, _, err := client.Enterprise.GetEnterpriseRuleset(ctx, "e", 26110) + if err != nil { + t.Errorf("Enterprise.GetEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(26110)), + Name: "test ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + NodeID: Ptr("nid"), + Links: &RulesetLinks{ + Self: &RulesetLink{HRef: Ptr("https://api.github.com/enterprises/e/rulesets/26110")}, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + }, + } + if !cmp.Equal(rulesets, want) { + t.Errorf("Enterprise.GetEnterpriseRuleset returned %+v, want %+v", rulesets, want) + } + + const methodName = "GetEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.GetEnterpriseRuleset(ctx, "e", 26110) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_UpdateEnterpriseRuleset(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets/26110", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "id": 26110, + "name": "test ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_mode": "none", + "node_id": "nid", + "_links": { + "self": { + "href": "https://api.github.com/enterprises/e/rulesets/26110" + } + }, + "conditions": { + "organization_name": { + "include": [ + "important_organization", + "another_important_organization" + ], + "exclude": [ + "unimportant_organization" + ] + }, + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + } + ] + }`) + }) + + ctx := context.Background() + rulesets, _, err := client.Enterprise.UpdateEnterpriseRuleset(ctx, "e", 26110, Ruleset{ + Name: "test ruleset", + Target: Ptr("branch"), + Enforcement: "active", + Conditions: &RulesetConditions{ + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + }, + }) + if err != nil { + t.Errorf("Enterprise.UpdateEnterpriseRuleset returned error: %v", err) + } + + want := &Ruleset{ + ID: Ptr(int64(26110)), + Name: "test ruleset", + Target: Ptr("branch"), + SourceType: Ptr("Enterprise"), + Source: "e", + Enforcement: "active", + NodeID: Ptr("nid"), + Links: &RulesetLinks{ + Self: &RulesetLink{HRef: Ptr("https://api.github.com/enterprises/e/rulesets/26110")}, + }, + Conditions: &RulesetConditions{ + OrganizationName: &RulesetOrganizationNamesConditionParameters{ + Include: []string{"important_organization", "another_important_organization"}, + Exclude: []string{"unimportant_organization"}, + }, + RepositoryName: &RulesetRepositoryNamesConditionParameters{ + Include: []string{"important_repository", "another_important_repository"}, + Exclude: []string{"unimportant_repository"}, + Protected: Ptr(true), + }, + RefName: &RulesetRefConditionParameters{ + Include: []string{"refs/heads/main", "refs/heads/master"}, + Exclude: []string{"refs/heads/dev*"}, + }, + }, + Rules: []*RepositoryRule{ + NewCreationRule(), + }, + } + if !cmp.Equal(rulesets, want) { + t.Errorf("Enterprise.UpdateEnterpriseRuleset returned %+v, want %+v", rulesets, want) + } + + const methodName = "UpdateEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.UpdateEnterpriseRuleset(ctx, "e", 26110, Ruleset{}) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_UpdateEnterpriseRulesetClearBypassActor(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets/26110", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "id": 26110, + "name": "test ruleset", + "target": "branch", + "source_type": "Enterprise", + "source": "e", + "enforcement": "active", + "bypass_mode": "none", + "conditions": { + "organization_name": { + "include": [ + "important_organization", + "another_important_organization" + ], + "exclude": [ + "unimportant_organization" + ] + }, + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + } + ] + }`) + }) + + ctx := context.Background() + + _, err := client.Enterprise.UpdateEnterpriseRulesetClearBypassActor(ctx, "e", 26110) + if err != nil { + t.Errorf("Enterprise.UpdateEnterpriseRulesetClearBypassActor returned error: %v \n", err) + } + + const methodName = "UpdateEnterpriseRulesetClearBypassActor" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Enterprise.UpdateEnterpriseRulesetClearBypassActor(ctx, "e", 26110) + }) +} + +func TestEnterpriseService_DeleteEnterpriseRuleset(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/rulesets/26110", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + ctx := context.Background() + _, err := client.Enterprise.DeleteEnterpriseRuleset(ctx, "e", 26110) + if err != nil { + t.Errorf("Enterprise.DeleteEnterpriseRuleset returned error: %v", err) + } + + const methodName = "DeleteEnterpriseRuleset" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Enterprise.DeleteEnterpriseRuleset(ctx, "e", 26110) + }) +} diff --git a/github/event_types_test.go b/github/event_types_test.go index 66d4e4989a7..b65d792e9bf 100644 --- a/github/event_types_test.go +++ b/github/event_types_test.go @@ -8651,7 +8651,6 @@ func TestRepositoryDispatchEvent_Marshal(t *testing.T) { Branch: Ptr("b"), ClientPayload: jsonMsg, Repo: &Repository{ - ID: Ptr(int64(1)), URL: Ptr("s"), Name: Ptr("n"), @@ -9770,11 +9769,12 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { PullRequest: &RepositoryRulesetPullRequestRule{ Type: "pull_request", Parameters: &PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }, }, RequiredStatusChecks: &RepositoryRulesetRequiredStatusChecksRule{ @@ -9977,11 +9977,12 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { PullRequest: &RepositoryRulesetPullRequestRule{ Type: "pull_request", Parameters: &PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }, }, RequiredStatusChecks: &RepositoryRulesetRequiredStatusChecksRule{ @@ -10131,11 +10132,12 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { PullRequest: &RepositoryRulesetPullRequestRule{ Type: "pull_request", Parameters: &PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }, }, RequiredStatusChecks: &RepositoryRulesetRequiredStatusChecksRule{ @@ -10285,11 +10287,12 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { PullRequest: &RepositoryRulesetPullRequestRule{ Type: "pull_request", Parameters: &PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }, }, RequiredStatusChecks: &RepositoryRulesetRequiredStatusChecksRule{ @@ -10630,6 +10633,7 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { "pull_request": { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -10863,6 +10867,7 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { "pull_request": { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -11024,6 +11029,7 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { "pull_request": { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -11186,6 +11192,7 @@ func TestRepositoryRulesetEvent_Marshal(t *testing.T) { "pull_request": { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, diff --git a/github/github-accessors.go b/github/github-accessors.go index cf70ac59493..448154a47f4 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -21974,6 +21974,22 @@ func (r *Ruleset) GetUpdatedAt() Timestamp { return *r.UpdatedAt } +// GetOrganizationID returns the OrganizationID field. +func (r *RulesetConditions) GetOrganizationID() *RulesetOrganizationIDsConditionParameters { + if r == nil { + return nil + } + return r.OrganizationID +} + +// GetOrganizationName returns the OrganizationName field. +func (r *RulesetConditions) GetOrganizationName() *RulesetOrganizationNamesConditionParameters { + if r == nil { + return nil + } + return r.OrganizationName +} + // GetRefName returns the RefName field. func (r *RulesetConditions) GetRefName() *RulesetRefConditionParameters { if r == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index ff7530bcc0a..1e2a53f1601 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -28164,6 +28164,22 @@ func TestRuleset_GetUpdatedAt(tt *testing.T) { r.GetUpdatedAt() } +func TestRulesetConditions_GetOrganizationID(tt *testing.T) { + tt.Parallel() + r := &RulesetConditions{} + r.GetOrganizationID() + r = nil + r.GetOrganizationID() +} + +func TestRulesetConditions_GetOrganizationName(tt *testing.T) { + tt.Parallel() + r := &RulesetConditions{} + r.GetOrganizationName() + r = nil + r.GetOrganizationName() +} + func TestRulesetConditions_GetRefName(tt *testing.T) { tt.Parallel() r := &RulesetConditions{} diff --git a/github/orgs_rules.go b/github/orgs_rules.go index 37c06a7333c..b0773fab917 100644 --- a/github/orgs_rules.go +++ b/github/orgs_rules.go @@ -37,21 +37,21 @@ func (s *OrganizationsService) GetAllOrganizationRulesets(ctx context.Context, o // GitHub API docs: https://docs.github.com/rest/orgs/rules#create-an-organization-repository-ruleset // //meta:operation POST /orgs/{org}/rulesets -func (s *OrganizationsService) CreateOrganizationRuleset(ctx context.Context, org string, rs *Ruleset) (*Ruleset, *Response, error) { +func (s *OrganizationsService) CreateOrganizationRuleset(ctx context.Context, org string, ruleset Ruleset) (*Ruleset, *Response, error) { u := fmt.Sprintf("orgs/%v/rulesets", org) - req, err := s.client.NewRequest("POST", u, rs) + req, err := s.client.NewRequest("POST", u, ruleset) if err != nil { return nil, nil, err } - var ruleset *Ruleset - resp, err := s.client.Do(ctx, req, &ruleset) + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) if err != nil { return nil, resp, err } - return ruleset, resp, nil + return rs, resp, nil } // GetOrganizationRuleset gets a ruleset from the specified organization. @@ -81,21 +81,46 @@ func (s *OrganizationsService) GetOrganizationRuleset(ctx context.Context, org s // GitHub API docs: https://docs.github.com/rest/orgs/rules#update-an-organization-repository-ruleset // //meta:operation PUT /orgs/{org}/rulesets/{ruleset_id} -func (s *OrganizationsService) UpdateOrganizationRuleset(ctx context.Context, org string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { +func (s *OrganizationsService) UpdateOrganizationRuleset(ctx context.Context, org string, rulesetID int64, ruleset Ruleset) (*Ruleset, *Response, error) { u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID) - req, err := s.client.NewRequest("PUT", u, rs) + req, err := s.client.NewRequest("PUT", u, ruleset) if err != nil { return nil, nil, err } - var ruleset *Ruleset - resp, err := s.client.Do(ctx, req, &ruleset) + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) if err != nil { return nil, resp, err } - return ruleset, resp, nil + return rs, resp, nil +} + +// UpdateOrganizationRulesetClearBypassActor clears the ruleset bypass actors for a ruleset for the specified repository. +// +// This function is necessary as the UpdateOrganizationRuleset function does not marshal ByPassActor if passed as an empty array. +// +// GitHub API docs: https://docs.github.com/rest/orgs/rules#update-an-organization-repository-ruleset +// +//meta:operation PUT /orgs/{org}/rulesets/{ruleset_id} +func (s *OrganizationsService) UpdateOrganizationRulesetClearBypassActor(ctx context.Context, org string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("orgs/%v/rulesets/%v", org, rulesetID) + + rsClearBypassActor := rulesetClearBypassActors{} + + req, err := s.client.NewRequest("PUT", u, rsClearBypassActor) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil } // DeleteOrganizationRuleset deletes a ruleset from the specified organization. diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go index ebc559c640c..13afd4f058e 100644 --- a/github/orgs_rules_test.go +++ b/github/orgs_rules_test.go @@ -138,6 +138,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -220,7 +221,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) }) ctx := context.Background() - ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", &Ruleset{ + ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{ ID: Ptr(int64(21)), Name: "ruleset", Target: Ptr("branch"), @@ -256,11 +257,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -351,11 +353,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -413,7 +416,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T) const methodName = "CreateOrganizationRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", nil) + got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -489,6 +492,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing. { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -571,7 +575,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing. }) ctx := context.Background() - ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", &Ruleset{ + ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{ ID: Ptr(int64(21)), Name: "ruleset", Target: Ptr("branch"), @@ -613,11 +617,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing. }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -714,11 +719,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing. }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -776,7 +782,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing. const methodName = "CreateOrganizationRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", nil) + got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -845,6 +851,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { { "type": "pull_request", "parameters": { + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -927,7 +934,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { }) ctx := context.Background() - ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", &Ruleset{ + ruleset, _, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{ ID: Ptr(int64(21)), Name: "ruleset", Target: Ptr("branch"), @@ -961,11 +968,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -1054,11 +1062,12 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { }), NewRequiredSignaturesRule(), NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, + DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: true, - DismissStaleReviewsOnPush: true, }), NewRequiredStatusChecksRule(&RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []RuleRequiredStatusChecks{ @@ -1116,7 +1125,7 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) { const methodName = "CreateOrganizationRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", nil) + got, resp, err := client.Organizations.CreateOrganizationRuleset(ctx, "o", Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -1360,7 +1369,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) { }) ctx := context.Background() - rulesets, _, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, &Ruleset{ + rulesets, _, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, Ruleset{ Name: "test ruleset", Target: Ptr("branch"), Enforcement: "active", @@ -1379,7 +1388,6 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) { NewCreationRule(), }, }) - if err != nil { t.Errorf("Organizations.UpdateOrganizationRuleset returned error: %v", err) } @@ -1417,7 +1425,7 @@ func TestOrganizationsService_UpdateOrganizationRuleset(t *testing.T) { const methodName = "UpdateOrganizationRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, nil) + got, resp, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -1468,7 +1476,7 @@ func TestOrganizationsService_UpdateOrganizationRulesetWithRepoProp(t *testing.T }) ctx := context.Background() - rulesets, _, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, &Ruleset{ + rulesets, _, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, Ruleset{ Name: "test ruleset", Target: Ptr("branch"), Enforcement: "active", @@ -1488,7 +1496,6 @@ func TestOrganizationsService_UpdateOrganizationRulesetWithRepoProp(t *testing.T NewCreationRule(), }, }) - if err != nil { t.Errorf("Organizations.UpdateOrganizationRuleset returned error: %v", err) } @@ -1527,7 +1534,7 @@ func TestOrganizationsService_UpdateOrganizationRulesetWithRepoProp(t *testing.T const methodName = "UpdateOrganizationRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, nil) + got, resp, err := client.Organizations.UpdateOrganizationRuleset(ctx, "o", 26110, Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -1535,6 +1542,63 @@ func TestOrganizationsService_UpdateOrganizationRulesetWithRepoProp(t *testing.T }) } +func TestOrganizationsService_UpdateOrganizationRulesetClearBypassActor(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/rulesets/26110", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "id": 26110, + "name": "test ruleset", + "target": "branch", + "source_type": "Organization", + "source": "o", + "enforcement": "active", + "bypass_mode": "none", + "conditions": { + "repository_name": { + "include": [ + "important_repository", + "another_important_repository" + ], + "exclude": [ + "unimportant_repository" + ], + "protected": true + }, + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + } + ] + }`) + }) + + ctx := context.Background() + + _, err := client.Organizations.UpdateOrganizationRulesetClearBypassActor(ctx, "o", 26110) + if err != nil { + t.Errorf("Organizations.UpdateOrganizationRulesetClearBypassActor returned error: %v \n", err) + } + + const methodName = "UpdateOrganizationRulesetClearBypassActor" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Organizations.UpdateOrganizationRulesetClearBypassActor(ctx, "o", 26110) + }) +} + func TestOrganizationsService_DeleteOrganizationRuleset(t *testing.T) { t.Parallel() client, mux, _ := setup(t) diff --git a/github/repos_rules.go b/github/repos_rules.go index b113553a247..60e9739cdf7 100644 --- a/github/repos_rules.go +++ b/github/repos_rules.go @@ -11,6 +11,16 @@ import ( "fmt" ) +// MergeMethod models a GitHub merge method. +type MergeMethod string + +// This is the set of GitHub merge methods. +const ( + MergeMethodMerge MergeMethod = "merge" + MergeMethodRebase MergeMethod = "rebase" + MergeMethodSquash MergeMethod = "squash" +) + // BypassActor represents the bypass actors from a ruleset. type BypassActor struct { ActorID *int64 `json:"actor_id,omitempty"` @@ -36,14 +46,14 @@ type RulesetRefConditionParameters struct { Exclude []string `json:"exclude"` } -// RulesetRepositoryNamesConditionParameters represents the conditions object for repository_names. +// RulesetRepositoryNamesConditionParameters represents the conditions object for repository_name. type RulesetRepositoryNamesConditionParameters struct { Include []string `json:"include"` Exclude []string `json:"exclude"` Protected *bool `json:"protected,omitempty"` } -// RulesetRepositoryIDsConditionParameters represents the conditions object for repository_ids. +// RulesetRepositoryIDsConditionParameters represents the conditions object for repository_id. type RulesetRepositoryIDsConditionParameters struct { RepositoryIDs []int64 `json:"repository_ids,omitempty"` } @@ -61,6 +71,17 @@ type RulesetRepositoryPropertyConditionParameters struct { Exclude []RulesetRepositoryPropertyTargetParameters `json:"exclude"` } +// RulesetOrganizationNamesConditionParameters represents the conditions object for organization_name. +type RulesetOrganizationNamesConditionParameters struct { + Include []string `json:"include"` + Exclude []string `json:"exclude"` +} + +// RulesetOrganizationIDsConditionParameters represents the conditions object for organization_id. +type RulesetOrganizationIDsConditionParameters struct { + OrganizationIDs []int64 `json:"organization_ids,omitempty"` +} + // RulesetConditions represents the conditions object in a ruleset. // Set either RepositoryName or RepositoryID or RepositoryProperty, not more than one. type RulesetConditions struct { @@ -68,6 +89,8 @@ type RulesetConditions struct { RepositoryName *RulesetRepositoryNamesConditionParameters `json:"repository_name,omitempty"` RepositoryID *RulesetRepositoryIDsConditionParameters `json:"repository_id,omitempty"` RepositoryProperty *RulesetRepositoryPropertyConditionParameters `json:"repository_property,omitempty"` + OrganizationName *RulesetOrganizationNamesConditionParameters `json:"organization_name,omitempty"` + OrganizationID *RulesetOrganizationIDsConditionParameters `json:"organization_id,omitempty"` } // RulePatternParameters represents the rule pattern parameters. @@ -112,11 +135,12 @@ type RequiredDeploymentEnvironmentsRuleParameters struct { // PullRequestRuleParameters represents the pull_request rule parameters. type PullRequestRuleParameters struct { - DismissStaleReviewsOnPush bool `json:"dismiss_stale_reviews_on_push"` - RequireCodeOwnerReview bool `json:"require_code_owner_review"` - RequireLastPushApproval bool `json:"require_last_push_approval"` - RequiredApprovingReviewCount int `json:"required_approving_review_count"` - RequiredReviewThreadResolution bool `json:"required_review_thread_resolution"` + AllowedMergeMethods []MergeMethod `json:"allowed_merge_methods"` + DismissStaleReviewsOnPush bool `json:"dismiss_stale_reviews_on_push"` + RequireCodeOwnerReview bool `json:"require_code_owner_review"` + RequireLastPushApproval bool `json:"require_last_push_approval"` + RequiredApprovingReviewCount int `json:"required_approving_review_count"` + RequiredReviewThreadResolution bool `json:"required_review_thread_resolution"` } // RuleRequiredStatusChecks represents the RequiredStatusChecks for the RequiredStatusChecksRuleParameters object. @@ -824,6 +848,11 @@ type rulesetNoOmitBypassActors struct { Rules []*RepositoryRule `json:"rules,omitempty"` } +// rulesetClearBypassActors is used to clear the bypass actors when modifying a GitHub ruleset object. +type rulesetClearBypassActors struct { + BypassActors []*BypassActor `json:"bypass_actors"` +} + // GetRulesForBranch gets all the rules that apply to the specified branch. // // GitHub API docs: https://docs.github.com/rest/repos/rules#get-rules-for-a-branch @@ -874,21 +903,21 @@ func (s *RepositoriesService) GetAllRulesets(ctx context.Context, owner, repo st // GitHub API docs: https://docs.github.com/rest/repos/rules#create-a-repository-ruleset // //meta:operation POST /repos/{owner}/{repo}/rulesets -func (s *RepositoriesService) CreateRuleset(ctx context.Context, owner, repo string, rs *Ruleset) (*Ruleset, *Response, error) { +func (s *RepositoriesService) CreateRuleset(ctx context.Context, owner, repo string, ruleset Ruleset) (*Ruleset, *Response, error) { u := fmt.Sprintf("repos/%v/%v/rulesets", owner, repo) - req, err := s.client.NewRequest("POST", u, rs) + req, err := s.client.NewRequest("POST", u, ruleset) if err != nil { return nil, nil, err } - var ruleset *Ruleset - resp, err := s.client.Do(ctx, req, &ruleset) + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) if err != nil { return nil, resp, err } - return ruleset, resp, nil + return rs, resp, nil } // GetRuleset gets a ruleset for the specified repository. @@ -919,49 +948,72 @@ func (s *RepositoriesService) GetRuleset(ctx context.Context, owner, repo string // GitHub API docs: https://docs.github.com/rest/repos/rules#update-a-repository-ruleset // //meta:operation PUT /repos/{owner}/{repo}/rulesets/{ruleset_id} -func (s *RepositoriesService) UpdateRuleset(ctx context.Context, owner, repo string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { +func (s *RepositoriesService) UpdateRuleset(ctx context.Context, owner, repo string, rulesetID int64, ruleset Ruleset) (*Ruleset, *Response, error) { u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) - req, err := s.client.NewRequest("PUT", u, rs) + req, err := s.client.NewRequest("PUT", u, ruleset) if err != nil { return nil, nil, err } - var ruleset *Ruleset - resp, err := s.client.Do(ctx, req, &ruleset) + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) if err != nil { return nil, resp, err } - return ruleset, resp, nil + return rs, resp, nil +} + +// UpdateRulesetClearBypassActor clears the ruleset bypass actors for a ruleset for the specified repository. +// +// This function is necessary as the UpdateRuleset function does not marshal ByPassActor if passed as an empty array. +// +// GitHub API docs: https://docs.github.com/rest/repos/rules#update-a-repository-ruleset +// +//meta:operation PUT /repos/{owner}/{repo}/rulesets/{ruleset_id} +func (s *RepositoriesService) UpdateRulesetClearBypassActor(ctx context.Context, owner, repo string, rulesetID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) + + rsClearBypassActor := rulesetClearBypassActors{} + + req, err := s.client.NewRequest("PUT", u, rsClearBypassActor) + if err != nil { + return nil, err + } + + resp, err := s.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + + return resp, nil } // UpdateRulesetNoBypassActor updates a ruleset for the specified repository. // -// This function is necessary as the UpdateRuleset function does not marshal ByPassActor if passed as nil or an empty array. +// This function is necessary as the UpdateRuleset function does not marshal ByPassActor if passed as an empty array. +// +// Deprecated: Use UpdateRulesetClearBypassActor instead. // // GitHub API docs: https://docs.github.com/rest/repos/rules#update-a-repository-ruleset // //meta:operation PUT /repos/{owner}/{repo}/rulesets/{ruleset_id} -func (s *RepositoriesService) UpdateRulesetNoBypassActor(ctx context.Context, owner, repo string, rulesetID int64, rs *Ruleset) (*Ruleset, *Response, error) { +func (s *RepositoriesService) UpdateRulesetNoBypassActor(ctx context.Context, owner, repo string, rulesetID int64, ruleset Ruleset) (*Ruleset, *Response, error) { u := fmt.Sprintf("repos/%v/%v/rulesets/%v", owner, repo, rulesetID) - rsNoBypassActor := &rulesetNoOmitBypassActors{} - - if rs != nil { - rsNoBypassActor = &rulesetNoOmitBypassActors{ - ID: rs.ID, - Name: rs.Name, - Target: rs.Target, - SourceType: rs.SourceType, - Source: rs.Source, - Enforcement: rs.Enforcement, - BypassActors: rs.BypassActors, - NodeID: rs.NodeID, - Links: rs.Links, - Conditions: rs.Conditions, - Rules: rs.Rules, - } + rsNoBypassActor := rulesetNoOmitBypassActors{ + ID: ruleset.ID, + Name: ruleset.Name, + Target: ruleset.Target, + SourceType: ruleset.SourceType, + Source: ruleset.Source, + Enforcement: ruleset.Enforcement, + BypassActors: ruleset.BypassActors, + NodeID: ruleset.NodeID, + Links: ruleset.Links, + Conditions: ruleset.Conditions, + Rules: ruleset.Rules, } req, err := s.client.NewRequest("PUT", u, rsNoBypassActor) @@ -969,13 +1021,13 @@ func (s *RepositoriesService) UpdateRulesetNoBypassActor(ctx context.Context, ow return nil, nil, err } - var ruleSet *Ruleset - resp, err := s.client.Do(ctx, req, &ruleSet) + var rs *Ruleset + resp, err := s.client.Do(ctx, req, &rs) if err != nil { return nil, resp, err } - return ruleSet, resp, nil + return rs, resp, nil } // DeleteRuleset deletes a ruleset for the specified repository. diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go index 1597b36e110..8ee8b9749ec 100644 --- a/github/repos_rules_test.go +++ b/github/repos_rules_test.go @@ -243,6 +243,7 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) { data: `{ "type":"pull_request", "parameters":{ + "allowed_merge_methods": ["rebase","squash"], "dismiss_stale_reviews_on_push": true, "require_code_owner_review": true, "require_last_push_approval": true, @@ -251,6 +252,7 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) { } }`, want: NewPullRequestRule(&PullRequestRuleParameters{ + AllowedMergeMethods: []MergeMethod{"rebase", "squash"}, DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireLastPushApproval: true, @@ -590,7 +592,7 @@ func TestRepositoriesService_CreateRuleset(t *testing.T) { }) ctx := context.Background() - ruleSet, _, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{ + ruleSet, _, err := client.Repositories.CreateRuleset(ctx, "o", "repo", Ruleset{ Name: "ruleset", Enforcement: "enabled", }) @@ -612,7 +614,7 @@ func TestRepositoriesService_CreateRuleset(t *testing.T) { const methodName = "CreateRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{}) + got, resp, err := client.Repositories.CreateRuleset(ctx, "o", "repo", Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -663,7 +665,7 @@ func TestRepositoriesService_CreateRulesetWithPushRules(t *testing.T) { }) ctx := context.Background() - ruleSet, _, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{ + ruleSet, _, err := client.Repositories.CreateRuleset(ctx, "o", "repo", Ruleset{ Name: "ruleset", Enforcement: "enabled", }) @@ -700,7 +702,7 @@ func TestRepositoriesService_CreateRulesetWithPushRules(t *testing.T) { const methodName = "CreateRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Repositories.CreateRuleset(ctx, "o", "repo", &Ruleset{}) + got, resp, err := client.Repositories.CreateRuleset(ctx, "o", "repo", Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -755,16 +757,10 @@ func TestRepositoriesService_GetRuleset(t *testing.T) { }) } -func TestRepositoriesService_UpdateRulesetNoBypassActor(t *testing.T) { +func TestRepositoriesService_UpdateRuleset(t *testing.T) { t.Parallel() client, mux, _ := setup(t) - rs := &Ruleset{ - Name: "ruleset", - Source: "o/repo", - Enforcement: "enabled", - } - mux.HandleFunc("/repos/o/repo/rulesets/42", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "PUT") fmt.Fprint(w, `{ @@ -777,11 +773,12 @@ func TestRepositoriesService_UpdateRulesetNoBypassActor(t *testing.T) { }) ctx := context.Background() - - ruleSet, _, err := client.Repositories.UpdateRulesetNoBypassActor(ctx, "o", "repo", 42, rs) - + ruleSet, _, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, Ruleset{ + Name: "ruleset", + Enforcement: "enabled", + }) if err != nil { - t.Errorf("Repositories.UpdateRulesetNoBypassActor returned error: %v \n", err) + t.Errorf("Repositories.UpdateRuleset returned error: %v", err) } want := &Ruleset{ @@ -793,13 +790,13 @@ func TestRepositoriesService_UpdateRulesetNoBypassActor(t *testing.T) { } if !cmp.Equal(ruleSet, want) { - t.Errorf("Repositories.UpdateRulesetNoBypassActor returned %+v, want %+v", ruleSet, want) + t.Errorf("Repositories.UpdateRuleset returned %+v, want %+v", ruleSet, want) } - const methodName = "UpdateRulesetNoBypassActor" + const methodName = "UpdateRuleset" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Repositories.UpdateRulesetNoBypassActor(ctx, "o", "repo", 42, nil) + got, resp, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -807,7 +804,7 @@ func TestRepositoriesService_UpdateRulesetNoBypassActor(t *testing.T) { }) } -func TestRepositoriesService_UpdateRuleset(t *testing.T) { +func TestRepositoriesService_UpdateRulesetClearBypassActor(t *testing.T) { t.Parallel() client, mux, _ := setup(t) @@ -819,16 +816,65 @@ func TestRepositoriesService_UpdateRuleset(t *testing.T) { "source_type": "Repository", "source": "o/repo", "enforcement": "enabled" + "conditions": { + "ref_name": { + "include": [ + "refs/heads/main", + "refs/heads/master" + ], + "exclude": [ + "refs/heads/dev*" + ] + } + }, + "rules": [ + { + "type": "creation" + } + ] }`) }) ctx := context.Background() - ruleSet, _, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, &Ruleset{ + + _, err := client.Repositories.UpdateRulesetClearBypassActor(ctx, "o", "repo", 42) + if err != nil { + t.Errorf("Repositories.UpdateRulesetClearBypassActor returned error: %v \n", err) + } + + const methodName = "UpdateRulesetClearBypassActor" + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.Repositories.UpdateRulesetClearBypassActor(ctx, "o", "repo", 42) + }) +} + +func TestRepositoriesService_UpdateRulesetNoBypassActor(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + rs := Ruleset{ Name: "ruleset", + Source: "o/repo", Enforcement: "enabled", + } + + mux.HandleFunc("/repos/o/repo/rulesets/42", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "id": 42, + "name": "ruleset", + "source_type": "Repository", + "source": "o/repo", + "enforcement": "enabled" + }`) }) + + ctx := context.Background() + + ruleSet, _, err := client.Repositories.UpdateRulesetNoBypassActor(ctx, "o", "repo", 42, rs) if err != nil { - t.Errorf("Repositories.UpdateRuleset returned error: %v", err) + t.Errorf("Repositories.UpdateRulesetNoBypassActor returned error: %v \n", err) } want := &Ruleset{ @@ -840,13 +886,13 @@ func TestRepositoriesService_UpdateRuleset(t *testing.T) { } if !cmp.Equal(ruleSet, want) { - t.Errorf("Repositories.UpdateRuleset returned %+v, want %+v", ruleSet, want) + t.Errorf("Repositories.UpdateRulesetNoBypassActor returned %+v, want %+v", ruleSet, want) } - const methodName = "UpdateRuleset" + const methodName = "UpdateRulesetNoBypassActor" testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Repositories.UpdateRuleset(ctx, "o", "repo", 42, nil) + got, resp, err := client.Repositories.UpdateRulesetNoBypassActor(ctx, "o", "repo", 42, Ruleset{}) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) }