From b1a2cf52ede8d27dcafdb229f98a07f85ba7b6da Mon Sep 17 00:00:00 2001 From: Chris <362037+ChrisAnn@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:42:22 +0000 Subject: [PATCH] [FEAT] Add support for "Require approval of the most recent reviewable push" (#2839) Add support for "Require approval of the most recent reviewable push" Co-authored-by: Nick Floyd <139819+nickfloyd@users.noreply.github.com> --- .../Clients/RepositoryBranchesClientTests.cs | 18 ++++++++++++++++++ .../OrganizationRepositoryWithTeamContext.cs | 6 +++--- .../Models/Request/BranchProtectionUpdate.cs | 18 ++++++++++++++---- Octokit/Models/Response/BranchProtection.cs | 13 ++++++++++--- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs index 038cf74d87..592ce1253d 100644 --- a/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs @@ -292,6 +292,7 @@ public async Task GetsBranchProtection() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Null(protection.Restrictions); @@ -320,6 +321,7 @@ public async Task GetsBranchProtectionWithRepositoryId() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Null(protection.Restrictions); @@ -349,6 +351,7 @@ public async Task GetsBranchProtectionForOrgRepo() Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(1, protection.Restrictions.Teams.Count); Assert.Equal(0, protection.Restrictions.Users.Count); @@ -373,6 +376,7 @@ public async Task GetsBranchProtectionForOrgRepoWithRepositoryId() Assert.Equal(0, protection.RequiredPullRequestReviews.DismissalRestrictions.Users.Count); Assert.True(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(1, protection.Restrictions.Teams.Count); Assert.Equal(0, protection.Restrictions.Users.Count); @@ -404,6 +408,7 @@ public async Task UpdatesBranchProtection() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(2, protection.RequiredPullRequestReviews.RequiredApprovingReviewCount); Assert.Null(protection.Restrictions); @@ -432,6 +437,7 @@ public async Task UpdatesBranchProtectionWithRepositoryId() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.True(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.True(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(2, protection.RequiredPullRequestReviews.RequiredApprovingReviewCount); Assert.Null(protection.Restrictions); @@ -461,6 +467,7 @@ public async Task UpdatesBranchProtectionForOrgRepo() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(2, protection.RequiredPullRequestReviews.RequiredApprovingReviewCount); Assert.Empty(protection.Restrictions.Teams); @@ -490,6 +497,7 @@ public async Task UpdatesBranchProtectionForOrgRepoWithRepositoryId() Assert.Null(protection.RequiredPullRequestReviews.DismissalRestrictions); Assert.False(protection.RequiredPullRequestReviews.DismissStaleReviews); Assert.False(protection.RequiredPullRequestReviews.RequireCodeOwnerReviews); + Assert.False(protection.RequiredPullRequestReviews.RequireLastPushApproval); Assert.Equal(2, protection.RequiredPullRequestReviews.RequiredApprovingReviewCount); Assert.Empty(protection.Restrictions.Teams); @@ -798,6 +806,7 @@ public async Task GetsReviewEnforcement() Assert.Null(requiredReviews.DismissalRestrictions); Assert.True(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); } } @@ -813,6 +822,7 @@ public async Task GetsReviewEnforcementWithRepositoryId() Assert.Null(requiredReviews.DismissalRestrictions); Assert.True(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); } } @@ -829,6 +839,7 @@ public async Task GetsReviewEnforcementForOrgRepo() Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); Assert.True(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); } } @@ -845,6 +856,7 @@ public async Task GetsReviewEnforcementForOrgRepoWithRepositoryId() Assert.Equal(0, requiredReviews.DismissalRestrictions.Users.Count); Assert.True(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); } } } @@ -865,6 +877,7 @@ public async Task UpdatesReviewEnforcement() Assert.Null(requiredReviews.DismissalRestrictions); Assert.False(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } @@ -883,6 +896,7 @@ public async Task UpdatesReviewEnforcementWithRepositoryId() Assert.Null(requiredReviews.DismissalRestrictions); Assert.False(requiredReviews.DismissStaleReviews); Assert.True(requiredReviews.RequireCodeOwnerReviews); + Assert.True(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } @@ -905,6 +919,7 @@ public async Task UpdatesReviewEnforcementForOrgRepo() Assert.Null(requiredReviews.DismissalRestrictions); Assert.False(requiredReviews.DismissStaleReviews); Assert.False(requiredReviews.RequireCodeOwnerReviews); + Assert.False(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } @@ -927,6 +942,7 @@ public async Task UpdatesReviewEnforcementForOrgRepoWithRepositoryId() Assert.Null(requiredReviews.DismissalRestrictions); Assert.False(requiredReviews.DismissStaleReviews); Assert.False(requiredReviews.RequireCodeOwnerReviews); + Assert.False(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } @@ -950,6 +966,7 @@ public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnly() Assert.Empty(requiredReviews.DismissalRestrictions.Users); Assert.False(requiredReviews.DismissStaleReviews); Assert.False(requiredReviews.RequireCodeOwnerReviews); + Assert.False(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } @@ -973,6 +990,7 @@ public async Task UpdatesReviewEnforcementForOrgRepoWithAdminOnlyWithRepositoryI Assert.Empty(requiredReviews.DismissalRestrictions.Users); Assert.False(requiredReviews.DismissStaleReviews); Assert.False(requiredReviews.RequireCodeOwnerReviews); + Assert.False(requiredReviews.RequireLastPushApproval); Assert.Equal(2, requiredReviews.RequiredApprovingReviewCount); } } diff --git a/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs b/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs index ab010f9386..d827f25ffa 100644 --- a/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs +++ b/Octokit.Tests.Integration/Helpers/OrganizationRepositoryWithTeamContext.cs @@ -28,7 +28,7 @@ internal async static Task ProtectDefaultBranch(this IGitHubClient client, Repos // Protect default branch var update = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), - new BranchProtectionRequiredReviewsUpdate(true, true, 3), + new BranchProtectionRequiredReviewsUpdate(true, true, 3, true), null, true, true, @@ -78,7 +78,7 @@ await client.Organization.Team.AddRepository( // Protect default branch var protection = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), - new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(new BranchProtectionTeamCollection { team.TeamName }), true, true, 3), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(new BranchProtectionTeamCollection { team.TeamName }), true, true, 3, true), new BranchProtectionPushRestrictionsUpdate(new BranchProtectionTeamCollection { team.TeamName }), true); await client.Repository.Branch.UpdateBranchProtection(repoContext.RepositoryOwner, repoContext.RepositoryName, repoContext.RepositoryDefaultBranch, protection); @@ -105,7 +105,7 @@ await client.Organization.Team.AddRepository( // Protect default branch var protection = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredStatusChecksUpdate(true, new[] { "build", "test" }), - new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(new BranchProtectionTeamCollection { contextOrgTeam.TeamName }), true, true, 3), + new BranchProtectionRequiredReviewsUpdate(new BranchProtectionRequiredReviewsDismissalRestrictionsUpdate(new BranchProtectionTeamCollection { contextOrgTeam.TeamName }), true, true, 3, true), new BranchProtectionPushRestrictionsUpdate(new BranchProtectionTeamCollection { contextOrgTeam.TeamName }), true); await client.Repository.Branch.UpdateBranchProtection(contextOrgRepo.RepositoryOwner, contextOrgRepo.RepositoryName, "main", protection); diff --git a/Octokit/Models/Request/BranchProtectionUpdate.cs b/Octokit/Models/Request/BranchProtectionUpdate.cs index 74d43d3610..d78de64d85 100644 --- a/Octokit/Models/Request/BranchProtectionUpdate.cs +++ b/Octokit/Models/Request/BranchProtectionUpdate.cs @@ -370,11 +370,13 @@ public class BranchProtectionRequiredReviewsUpdate /// Dismiss approved reviews automatically when a new commit is pushed. /// Blocks merge until code owners have reviewed. /// Specify the number of reviewers required to approve pull requests. Use a number between 1 and 6. - public BranchProtectionRequiredReviewsUpdate(bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount) + /// Whether the most recent push must be approved by someone other than the person who pushed it. Default: false. + public BranchProtectionRequiredReviewsUpdate(bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount, bool requireLastPushApproval = false) { DismissStaleReviews = dismissStaleReviews; RequireCodeOwnerReviews = requireCodeOwnerReviews; RequiredApprovingReviewCount = requiredApprovingReviewCount; + RequireLastPushApproval = requireLastPushApproval; } /// @@ -384,7 +386,8 @@ public BranchProtectionRequiredReviewsUpdate(bool dismissStaleReviews, bool requ /// Dismiss approved reviews automatically when a new commit is pushed. /// Blocks merge until code owners have reviewed. /// Specify the number of reviewers required to approve pull requests. Use a number between 1 and 6. - public BranchProtectionRequiredReviewsUpdate(BranchProtectionRequiredReviewsDismissalRestrictionsUpdate dismissalRestrictions, bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount) + /// Whether the most recent push must be approved by someone other than the person who pushed it. Default: false. + public BranchProtectionRequiredReviewsUpdate(BranchProtectionRequiredReviewsDismissalRestrictionsUpdate dismissalRestrictions, bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount, bool requireLastPushApproval = false) { Ensure.ArgumentNotNull(dismissalRestrictions, nameof(dismissalRestrictions)); @@ -392,6 +395,7 @@ public BranchProtectionRequiredReviewsUpdate(BranchProtectionRequiredReviewsDism DismissStaleReviews = dismissStaleReviews; RequireCodeOwnerReviews = requireCodeOwnerReviews; RequiredApprovingReviewCount = requiredApprovingReviewCount; + RequireLastPushApproval = requireLastPushApproval; } /// @@ -413,16 +417,22 @@ public BranchProtectionRequiredReviewsUpdate(BranchProtectionRequiredReviewsDism /// Specify the number of reviewers required to approve pull requests. Use a number between 1 and 6. /// public int RequiredApprovingReviewCount { get; protected set; } + + /// + /// Whether the most recent push must be approved by someone other than the person who pushed it. Default: false + /// + public bool RequireLastPushApproval { get; protected set; } internal string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2} RequiredApprovingReviewCount: {3}", + return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2} RequiredApprovingReviewCount: {3} RequireLastPushApproval: {4}", DismissalRestrictions?.DebuggerDisplay ?? "disabled", DismissStaleReviews, RequireCodeOwnerReviews, - RequiredApprovingReviewCount); + RequiredApprovingReviewCount, + RequireLastPushApproval); } } } diff --git a/Octokit/Models/Response/BranchProtection.cs b/Octokit/Models/Response/BranchProtection.cs index 13202d859e..29a3cde9ef 100644 --- a/Octokit/Models/Response/BranchProtection.cs +++ b/Octokit/Models/Response/BranchProtection.cs @@ -236,12 +236,13 @@ public class BranchProtectionRequiredReviews { public BranchProtectionRequiredReviews() { } - public BranchProtectionRequiredReviews(BranchProtectionRequiredReviewsDismissalRestrictions dismissalRestrictions, bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount) + public BranchProtectionRequiredReviews(BranchProtectionRequiredReviewsDismissalRestrictions dismissalRestrictions, bool dismissStaleReviews, bool requireCodeOwnerReviews, int requiredApprovingReviewCount, bool requireLastPushApproval) { DismissalRestrictions = dismissalRestrictions; DismissStaleReviews = dismissStaleReviews; RequireCodeOwnerReviews = requireCodeOwnerReviews; RequiredApprovingReviewCount = requiredApprovingReviewCount; + RequireLastPushApproval = requireLastPushApproval; } /// @@ -263,16 +264,22 @@ public BranchProtectionRequiredReviews(BranchProtectionRequiredReviewsDismissalR /// Specify the number of reviewers required to approve pull requests. Use a number between 1 and 6. /// public int RequiredApprovingReviewCount { get; private set; } + + /// + /// Whether the most recent push must be approved by someone other than the person who pushed it. Default: false + /// + public bool RequireLastPushApproval { get; protected set; } internal string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2} RequiredApprovingReviewCount: {3}", + return string.Format(CultureInfo.InvariantCulture, "DismissalRestrictions: {0} DismissStaleReviews: {1} RequireCodeOwnerReviews: {2} RequiredApprovingReviewCount: {3} RequireLastPushApproval: {4}", DismissalRestrictions?.DebuggerDisplay ?? "disabled", DismissStaleReviews, RequireCodeOwnerReviews, - RequiredApprovingReviewCount); + RequiredApprovingReviewCount, + RequireLastPushApproval); } } }