Repository: google/go-github
Pull request #3253 (2024-08-28 21:05:21)

feat!: Add merge queue parameters to repository ruleset
Fixes: #3225.

BREAKING CHANGE: `NewMergeQueueRule` now takes one parameter: `*MergeQueueRuleParameters`.

## Summary
closes: #3225 
(I'm the same author as the assignee of the issue @zhpeng811, but submitting the 
PR using my corporate account)

## Details
This PR adds merge queue parameter configurations to the repository branch ruleset.

an attempt was also made for organization ruleset but was later determined that GitHub currently does not support merge queue configurations at the organizational level. Configs related to merge queue in tests was removed as the result.

## Testing
### General
Ran the required scripts on step 4 of the CONTRIBUTING.md file
<details><summary>Details</summary>
<p>

```
❯ script/fmt.sh
❯ script/test.sh
testing .
?       github.com/google/go-github/v63/test/integration        [no test files]
ok      github.com/google/go-github/v63/github  (cached)        coverage: 99.4% of statements
        github.com/google/go-github/v63/test/fields             coverage: 0.0% of statements
testing example
        github.com/google/go-github/v63/example/actionpermissions               coverage: 0.0% of statements
        github.com/google/go-github/v63/example/basicauth               coverage: 0.0% of statements
        github.com/google/go-github/v63/example/codespaces/newusersecretwithxcrypto             coverage: 0.0% of statements
        github.com/google/go-github/v63/example/codespaces/newreposecretwithxcrypto             coverage: 0.0% of statements
        github.com/google/go-github/v63/example/appengine               coverage: 0.0% of statements
        github.com/google/go-github/v63/example/newfilewithappauth              coverage: 0.0% of statements
        github.com/google/go-github/v63/example/listenvironments                coverage: 0.0% of statements
        github.com/google/go-github/v63/example/commitpr                coverage: 0.0% of statements
        github.com/google/go-github/v63/example/migrations              coverage: 0.0% of statements
        github.com/google/go-github/v63/example/newrepo         coverage: 0.0% of statements
        github.com/google/go-github/v63/example/newreposecretwithxcrypto                coverage: 0.0% of statements
        github.com/google/go-github/v63/example/simple          coverage: 0.0% of statements
        github.com/google/go-github/v63/example/ratelimit               coverage: 0.0% of statements
        github.com/google/go-github/v63/example/tagprotection           coverage: 0.0% of statements
        github.com/google/go-github/v63/example/tokenauth               coverage: 0.0% of statements
        github.com/google/go-github/v63/example/topics          coverage: 0.0% of statements
testing scrape
ok      github.com/google/go-github/scrape      (cached)        coverage: 59.4% of statements
        github.com/google/go-github/scrape/example/scrape               coverage: 0.0% of statements
testing tools
ok      tools/metadata  (cached)        coverage: 81.7% of statements
❯ script/lint.sh
linting .
linting example
linting scrape
linting tools
validating generated files
```

</p>
</details> 


### Repository Ruleset 
Manual:
Tested with the following test code, both with and without including the specific parameters, and verified in the UI.
<details><summary>Expand to view code</summary>
<p>

```go
func main() {
	client := github.NewClient(nil).WithAuthToken(os.Getenv("GITHUB_TOKEN"))
	org := "<REPLACE_WITH_YOUR_ORG>"
	repo := "<REPLACE_WITH_YOUR_REPO>"

	ctx := context.Background()
	_, _, err := client.Repositories.CreateRuleset(ctx, org, repo, &github.Ruleset{
		Name:        "merge-queue-test-without-param",
		Enforcement: "active",
		Rules: []*github.RepositoryRule{
			github.NewMergeQueueRule(nil),
		},
	})
	if err != nil {
		fmt.Println(err)
	}
	_, _, err = client.Repositories.CreateRuleset(ctx, org, repo, &github.Ruleset{
		Name:        "merge-queue-test-with-param",
		Enforcement: "active",
		Rules: []*github.RepositoryRule{
			github.NewMergeQueueRule(&github.MergeQueueRuleParameters{
				CheckResponseTimeoutMinutes:  35,
				GroupingStrategy:             "HEADGREEN",
				MaxEntriesToBuild:            8,
				MaxEntriesToMerge:            4,
				MergeMethod:                  "SQUASH",
				MinEntriesToMerge:            2,
				MinEntriesToMergeWaitMinutes: 13,
			}),
		},
	})
	if err != nil {
		fmt.Println(err)
	}
}
```

</p>
</details> 

Unit Test:
A unit test already exist for merge queue without parameters, 2 more tests were added (1 success case and 1 failure case with the incorrect parameter type)

### Organization Ruleset
Was trying to create a org ruleset with merge queue rule using the following curl
<details><summary>Expand for curl</summary>
<p>

```bash
export ORG_NAME=<REPLACE_WITH_YOUR_ORG_NAME>

curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/orgs/$ORG_NAME/rulesets \
  -d '{"name":"merge queue test ruleset","target":"branch","enforcement":"active","conditions":{"ref_name":{"include":["refs/heads/main","refs/heads/master"],"exclude":["refs/heads/dev*"]},"repository_name":{"include":["test1","test2"],"exclude":["test3"],"protected":true}},"rules":[{"type":"merge_queue"}]}'
```

</p>
</details> 

but got the following error:
```
{
  "message": "Validation Failed",
  "errors": [
    "Invalid rules: 'Merge queue'"
  ],
  "documentation_url": "https://docs.github.com/rest/orgs/rules#create-an-organization-repository-ruleset",
  "status": "422"
}
```

I cross referenced with the UI page and did not notice a branch rule for merge queue, which conflicts with the REST API documentation for org rulesets, so seems like GitHub currently does not support merge queue configuration on the org level ruleset. Reported the issue to GitHub community: https://github.com/orgs/community/discussions/137097

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/google/go-github/pull/3253?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 92.94%. Comparing base [(`2b8c7fa`)](https://app.codecov.io/gh/google/go-github/commit/2b8c7fa86fcd1cb4a89a44c8e7b5a49fe4be8a45?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) to head [(`02f3dc8`)](https://app.codecov.io/gh/google/go-github/commit/02f3dc8f96ffae752abd0f31a47af0ca36dc1ecb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> Report is 104 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3253      +/-   ##
==========================================
- Coverage   97.72%   92.94%   -4.78%     
==========================================
  Files         153      171      +18     
  Lines       13390    11663    -1727     
==========================================
- Hits        13085    10840    -2245     
- Misses        215      729     +514     
- Partials       90       94       +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/go-github/pull/3253?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

gmlewis: Thank you, @ali-kafel and @tomleicircle !
Merging.

Patch:
diff --git a/github/repos_rules.go b/github/repos_rules.go
index 3c6e8f01..a90555cc 100644
--- a/github/repos_rules.go
+++ b/github/repos_rules.go
@@ -110,6 +110,19 @@ type RuleRequiredStatusChecks struct {
 	IntegrationID *int64 `json:"integration_id,omitempty"`
 }
 
+// MergeQueueRuleParameters represents the merge_queue rule parameters.
+type MergeQueueRuleParameters struct {
+	CheckResponseTimeoutMinutes int `json:"check_response_timeout_minutes"`
+	// Possible values for GroupingStrategy are: ALLGREEN, HEADGREEN
+	GroupingStrategy  string `json:"grouping_strategy"`
+	MaxEntriesToBuild int    `json:"max_entries_to_build"`
+	MaxEntriesToMerge int    `json:"max_entries_to_merge"`
+	// Possible values for MergeMethod are: MERGE, SQUASH, REBASE
+	MergeMethod                  string `json:"merge_method"`
+	MinEntriesToMerge            int    `json:"min_entries_to_merge"`
+	MinEntriesToMergeWaitMinutes int    `json:"min_entries_to_merge_wait_minutes"`
+}
+
 // RequiredStatusChecksRuleParameters represents the required_status_checks rule parameters.
 type RequiredStatusChecksRuleParameters struct {
 	DoNotEnforceOnCreate             bool                       `json:"do_not_enforce_on_create"`
@@ -154,7 +167,7 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 	r.Type = RepositoryRule.Type
 
 	switch RepositoryRule.Type {
-	case "creation", "deletion", "merge_queue", "non_fast_forward", "required_linear_history", "required_signatures":
+	case "creation", "deletion", "non_fast_forward", "required_linear_history", "required_signatures":
 		r.Parameters = nil
 	case "update":
 		if RepositoryRule.Parameters == nil {
@@ -170,7 +183,20 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 		rawParams := json.RawMessage(bytes)
 
 		r.Parameters = &rawParams
+	case "merge_queue":
+		if RepositoryRule.Parameters == nil {
+			r.Parameters = nil
+			return nil
+		}
+		params := MergeQueueRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
 
+		r.Parameters = &rawParams
 	case "required_deployments":
 		params := RequiredDeploymentEnvironmentsRuleParameters{}
 		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
@@ -240,7 +266,17 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 }
 
 // NewMergeQueueRule creates a rule to only allow merges via a merge queue.
-func NewMergeQueueRule() (rule *RepositoryRule) {
+func NewMergeQueueRule(params *MergeQueueRuleParameters) (rule *RepositoryRule) {
+	if params != nil {
+		bytes, _ := json.Marshal(params)
+
+		rawParams := json.RawMessage(bytes)
+
+		return &RepositoryRule{
+			Type:       "merge_queue",
+			Parameters: &rawParams,
+		}
+	}
 	return &RepositoryRule{
 		Type: "merge_queue",
 	}


Test patch:
diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go
index 13d4c76d..435e7e58 100644
--- a/github/orgs_rules_test.go
+++ b/github/orgs_rules_test.go
@@ -123,9 +123,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 			  {
 				"type": "deletion"
 			  },
-			  {
-				"type": "merge_queue"
-			  },
 			  {
 				"type": "required_linear_history"
 			  },
@@ -241,7 +238,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
@@ -328,7 +324,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
@@ -449,9 +444,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 			  {
 				"type": "deletion"
 			  },
-			  {
-				"type": "merge_queue"
-			  },
 			  {
 				"type": "required_linear_history"
 			  },
@@ -574,7 +566,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
@@ -668,7 +659,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
@@ -781,9 +771,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 			  {
 				"type": "deletion"
 			  },
-			  {
-				"type": "merge_queue"
-			  },
 			  {
 				"type": "required_linear_history"
 			  },
@@ -897,7 +884,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
@@ -982,7 +968,6 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 				UpdateAllowsFetchAndMerge: true,
 			}),
 			NewDeletionRule(),
-			NewMergeQueueRule(),
 			NewRequiredLinearHistoryRule(),
 			NewRequiredDeploymentsRule(&RequiredDeploymentEnvironmentsRuleParameters{
 				RequiredDeploymentEnvironments: []string{"test"},
diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go
index 92f7259d..8e123ed0 100644
--- a/github/repos_rules_test.go
+++ b/github/repos_rules_test.go
@@ -74,6 +74,48 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) {
 				Parameters: nil,
 			},
 		},
+		"Valid merge_queue with params": {
+			data: `{
+				"type":"merge_queue",
+				"parameters":{
+					"check_response_timeout_minutes": 35,
+					"grouping_strategy": "HEADGREEN",
+					"max_entries_to_build": 8,
+					"max_entries_to_merge": 4,
+					"merge_method": "SQUASH",
+					"min_entries_to_merge": 2,
+					"min_entries_to_merge_wait_minutes": 13
+				}
+			}`,
+			want: NewMergeQueueRule(&MergeQueueRuleParameters{
+				CheckResponseTimeoutMinutes:  35,
+				GroupingStrategy:             "HEADGREEN",
+				MaxEntriesToBuild:            8,
+				MaxEntriesToMerge:            4,
+				MergeMethod:                  "SQUASH",
+				MinEntriesToMerge:            2,
+				MinEntriesToMergeWaitMinutes: 13,
+			}),
+		},
+		"Invalid merge_queue with params": {
+			data: `{
+				"type":"merge_queue",
+				"parameters":{
+					"check_response_timeout_minutes": "35",
+					"grouping_strategy": "HEADGREEN",
+					"max_entries_to_build": "8",
+					"max_entries_to_merge": "4",
+					"merge_method": "SQUASH",
+					"min_entries_to_merge": "2",
+					"min_entries_to_merge_wait_minutes": "13"
+				}
+			}`,
+			want: &RepositoryRule{
+				Type:       "merge_queue",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
 		"Valid non_fast_forward": {
 			data: `{"type":"non_fast_forward"}`,
 			want: &RepositoryRule{