From ef0da2f84d6d7c9171f6a37bc0d50abd05c49778 Mon Sep 17 00:00:00 2001 From: Ryan Gribble Date: Mon, 8 Aug 2016 22:00:37 +1000 Subject: [PATCH] Create RepositoryBranchesClient (#1437) * Tidy up location of existing EditBranch tests * Create RepositoryBranchesClient and move the GetBranch GetAllBranches and EditBranch methods to it, obsoleting the old ones * Add tests for the new RepositoryBranchesClient (keeping old tests for RepositoriesClient around for now) * Disable obsolete warning on reactive client temporarily * Create observable repository branches client and move GetBranch, GetAllBranches, EditBranch methods to it, obsoleting the old ones * Add tests for observable repository branches client, leave old tests in place for now * Fix projects... * Fix whitespace --- .../Clients/IObservableRepositoriesClient.cs | 9 + .../IObservableRepositoryBranchesClient.cs | 96 ++++++ .../Clients/ObservableRepositoriesClient.cs | 49 ++- .../ObservableRepositoryBranchesClient.cs | 151 +++++++++ Octokit.Reactive/Octokit.Reactive-Mono.csproj | 2 + .../Octokit.Reactive-MonoAndroid.csproj | 2 + .../Octokit.Reactive-Monotouch.csproj | 2 + Octokit.Reactive/Octokit.Reactive.csproj | 2 + .../Clients/BranchesClientTests.cs | 125 -------- .../Clients/RepositoriesClientTests.cs | 105 +++++++ .../Clients/RepositoryBranchesClientTests.cs | 293 ++++++++++++++++++ .../Octokit.Tests.Integration.csproj | 2 +- .../Clients/RepositoryBranchesClientTests.cs | 205 ++++++++++++ Octokit.Tests/Octokit.Tests.csproj | 2 + .../ObservableRepositoriesClientTests.cs | 8 +- ...ObservableRepositoryBranchesClientTests.cs | 200 ++++++++++++ Octokit/Clients/IRepositoriesClient.cs | 19 +- Octokit/Clients/IRepositoryBranchesClient.cs | 101 ++++++ Octokit/Clients/RepositoriesClient.cs | 57 ++-- Octokit/Clients/RepositoryBranchesClient.cs | 155 +++++++++ Octokit/Octokit-Mono.csproj | 2 + Octokit/Octokit-MonoAndroid.csproj | 2 + Octokit/Octokit-Monotouch.csproj | 2 + Octokit/Octokit-Portable.csproj | 2 + Octokit/Octokit-netcore45.csproj | 2 + Octokit/Octokit.csproj | 2 + 26 files changed, 1424 insertions(+), 173 deletions(-) create mode 100644 Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs create mode 100644 Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs delete mode 100644 Octokit.Tests.Integration/Clients/BranchesClientTests.cs create mode 100644 Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs create mode 100644 Octokit.Tests/Clients/RepositoryBranchesClientTests.cs create mode 100644 Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs create mode 100644 Octokit/Clients/IRepositoryBranchesClient.cs create mode 100644 Octokit/Clients/RepositoryBranchesClient.cs diff --git a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs index e5d79afab0..8f8022be71 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs @@ -150,6 +150,15 @@ public interface IObservableRepositoriesClient /// A of . IObservable GetAllForOrg(string organization, ApiOptions options); + /// + /// Client for managing branches in a repository. + /// + /// + /// See the Branches API documentation for more details + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + IObservableRepositoryBranchesClient Branch { get; } + /// /// A client for GitHub's Commit Status API. /// diff --git a/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs new file mode 100644 index 0000000000..49e0a7c82d --- /dev/null +++ b/Octokit.Reactive/Clients/IObservableRepositoryBranchesClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Octokit.Reactive +{ + /// + /// A client for GitHub's Repository Branches API. + /// + /// + /// See the Repository Branches API documentation for more details. + /// + public interface IObservableRepositoryBranchesClient + { + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + IObservable GetAll(string owner, string name); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + IObservable GetAll(int repositoryId); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Options for changing the API response + IObservable GetAll(string owner, string name, ApiOptions options); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// Options for changing the API response + IObservable GetAll(int repositoryId, ApiOptions options); + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + IObservable Get(string owner, string name, string branch); + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + IObservable Get(int repositoryId, string branch); + + /// + /// Edit the specified branch with the values given in + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + IObservable Edit(string owner, string name, string branch, BranchUpdate update); + + /// + /// Edit the specified branch with the values given in + /// + /// The Id of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + IObservable Edit(int repositoryId, string branch, BranchUpdate update); + } +} diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs index 7f68b48849..dbe5c5546c 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; @@ -26,6 +27,7 @@ public ObservableRepositoriesClient(IGitHubClient client) Deployment = new ObservableDeploymentsClient(client); Statistics = new ObservableStatisticsClient(client); PullRequest = new ObservablePullRequestsClient(client); + Branch = new ObservableRepositoryBranchesClient(client); Comment = new ObservableRepositoryCommentsClient(client); Commit = new ObservableRepositoryCommitsClient(client); Release = new ObservableReleasesClient(client); @@ -323,69 +325,73 @@ public IObservable GetAllForOrg(string organization, ApiOptions opti /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use ObservableRepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public IObservable GetAllBranches(string owner, string name) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); - return GetAllBranches(owner, name, ApiOptions.None); + return Branch.GetAll(owner, name, ApiOptions.None); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use ObservableRepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public IObservable GetAllBranches(int repositoryId) { - return GetAllBranches(repositoryId, ApiOptions.None); + return Branch.GetAll(repositoryId, ApiOptions.None); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository /// Options for changing the API response /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use ObservableRepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public IObservable GetAllBranches(string owner, string name, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.RepoBranches(owner, name), options); + return Branch.GetAll(owner, name, options); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository /// Options for changing the API response /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use ObservableRepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public IObservable GetAllBranches(int repositoryId, ApiOptions options) { Ensure.ArgumentNotNull(options, "options"); - return _connection.GetAndFlattenAllPages(ApiUrls.RepoBranches(repositoryId), options); + return Branch.GetAll(repositoryId, options); } /// @@ -702,35 +708,37 @@ public IObservable GetAllTags(int repositoryId, ApiOptions option /// Gets the specified branch. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository /// The name of the branch /// The specified + [Obsolete("Please use ObservableRepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] public IObservable GetBranch(string owner, string name, string branchName) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(branchName, "branchName"); - return _client.GetBranch(owner, name, branchName).ToObservable(); + return Branch.Get(owner, name, branchName); } /// /// Gets the specified branch. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository /// The name of the branch /// The specified + [Obsolete("Please use ObservableRepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] public IObservable GetBranch(int repositoryId, string branchName) { Ensure.ArgumentNotNullOrEmptyString(branchName, "branchName"); - return _client.GetBranch(repositoryId, branchName).ToObservable(); + return Branch.Get(repositoryId, branchName); } /// @@ -770,7 +778,7 @@ public IObservable Edit(int repositoryId, RepositoryUpdate update) /// The name of the branch /// New values to update the branch with /// The updated - [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + [Obsolete("Please use RepositoriesClient.Branch.Edit() instead. This method will be removed in a future version")] public IObservable EditBranch(string owner, string name, string branch, BranchUpdate update) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); @@ -778,7 +786,7 @@ public IObservable EditBranch(string owner, string name, string branch, Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); Ensure.ArgumentNotNull(update, "update"); - return _client.EditBranch(owner, name, branch, update).ToObservable(); + return Branch.Edit(owner, name, branch, update); } /// @@ -788,13 +796,13 @@ public IObservable EditBranch(string owner, string name, string branch, /// The name of the branch /// New values to update the branch with /// The updated - [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + [Obsolete("Please use RepositoriesClient.Branch.Edit() instead. This method will be removed in a future version")] public IObservable EditBranch(int repositoryId, string branch, BranchUpdate update) { Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); Ensure.ArgumentNotNull(update, "update"); - return _client.EditBranch(repositoryId, branch, update).ToObservable(); + return Branch.Edit(repositoryId, branch, update); } /// @@ -810,6 +818,15 @@ public IObservable Compare(string owner, string name, string @bas return _client.Commit.Compare(owner, name, @base, head).ToObservable(); } + /// + /// A client for GitHub's Repository Branches API. + /// + /// + /// See the Branches API documentation for more details + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + public IObservableRepositoryBranchesClient Branch { get; private set; } + /// /// A client for GitHub's Repo Collaborators. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs new file mode 100644 index 0000000000..269da55fca --- /dev/null +++ b/Octokit.Reactive/Clients/ObservableRepositoryBranchesClient.cs @@ -0,0 +1,151 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using Octokit.Reactive.Internal; + +namespace Octokit.Reactive +{ + public class ObservableRepositoryBranchesClient : IObservableRepositoryBranchesClient + { + readonly IRepositoryBranchesClient _client; + readonly IConnection _connection; + + public ObservableRepositoryBranchesClient(IGitHubClient client) + { + Ensure.ArgumentNotNull(client, "client"); + + _client = client.Repository.Branch; + _connection = client.Connection; + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + public IObservable GetAll(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return GetAll(owner, name, ApiOptions.None); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + public IObservable GetAll(int repositoryId) + { + return GetAll(repositoryId, ApiOptions.None); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Options for changing the API response + public IObservable GetAll(string owner, string name, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(options, "options"); + + return _connection.GetAndFlattenAllPages(ApiUrls.RepoBranches(owner, name), options); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// Options for changing the API response + public IObservable GetAll(int repositoryId, ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + + return _connection.GetAndFlattenAllPages(ApiUrls.RepoBranches(repositoryId), options); + } + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + public IObservable Get(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.Get(owner, name, branch).ToObservable(); + } + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + public IObservable Get(int repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return _client.Get(repositoryId, branch).ToObservable(); + } + + /// + /// Edit the specified branch with the values given in + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + public IObservable Edit(string owner, string name, string branch, BranchUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, "update"); + + return _client.Edit(owner, name, branch, update).ToObservable(); + } + + /// + /// Edit the specified branch with the values given in + /// + /// The Id of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + public IObservable Edit(int repositoryId, string branch, BranchUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, "update"); + + return _client.Edit(repositoryId, branch, update).ToObservable(); + } + } +} diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj index c96ce61bfb..25287f129b 100644 --- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj @@ -194,6 +194,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj index 2b555cc715..38148d1db6 100644 --- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj +++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj @@ -210,6 +210,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj index 0431a56579..4d123580a2 100644 --- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj +++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj @@ -206,6 +206,8 @@ + + diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj index ae1cdaf97e..59ed1851ec 100644 --- a/Octokit.Reactive/Octokit.Reactive.csproj +++ b/Octokit.Reactive/Octokit.Reactive.csproj @@ -99,6 +99,8 @@ + + diff --git a/Octokit.Tests.Integration/Clients/BranchesClientTests.cs b/Octokit.Tests.Integration/Clients/BranchesClientTests.cs deleted file mode 100644 index f661518fde..0000000000 --- a/Octokit.Tests.Integration/Clients/BranchesClientTests.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Octokit; -using Octokit.Tests.Integration; -using Octokit.Tests.Integration.Helpers; -using Xunit; - -public class BranchesClientTests -{ - public class TheGetBranchesMethod - { - [IntegrationTest] - public async Task ReturnsBranches() - { - var github = Helper.GetAuthenticatedClient(); - - using (var context = await github.CreateRepositoryContext("public-repo")) - { - var branches = await github.Repository.GetAllBranches(context.Repository.Owner.Login, context.Repository.Name); - - Assert.NotEmpty(branches); - Assert.Equal(branches[0].Name, "master"); - Assert.NotNull(branches[0].Protection); - } - } - } - - public class TheEditBranchesMethod - { - private readonly IRepositoriesClient _fixture; - private readonly RepositoryContext _context; - - public TheEditBranchesMethod() - { - var github = Helper.GetAuthenticatedClient(); - _context = github.CreateRepositoryContext("source-repo").Result; - _fixture = github.Repository; - } - - public async Task CreateTheWorld() - { - // Set master branch to be protected, with some status checks - var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2" }); - - var update = new BranchUpdate(); - update.Protection = new BranchProtection(true, requiredStatusChecks); - - var newBranch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); - } - - [IntegrationTest] - public async Task ProtectsBranch() - { - // Set master branch to be protected, with some status checks - var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2", "check3" }); - - var update = new BranchUpdate(); - update.Protection = new BranchProtection(true, requiredStatusChecks); - - var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); - - // Ensure a branch object was returned - Assert.NotNull(branch); - - // Retrieve master branch - branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); - - // Assert the changes were made - Assert.Equal(branch.Protection.Enabled, true); - Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Everyone); - Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 3); - } - - [IntegrationTest] - public async Task RemoveStatusCheckEnforcement() - { - await CreateTheWorld(); - - // Remove status check enforcement - var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Off, new List { "check1" }); - - var update = new BranchUpdate(); - update.Protection = new BranchProtection(true, requiredStatusChecks); - - var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); - - // Ensure a branch object was returned - Assert.NotNull(branch); - - // Retrieve master branch - branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); - - // Assert the changes were made - Assert.Equal(branch.Protection.Enabled, true); - Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); - Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 1); - } - - [IntegrationTest] - public async Task UnprotectsBranch() - { - await CreateTheWorld(); - - // Unprotect branch - // Deliberately set Enforcement and Contexts to some values (these should be ignored) - var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1" }); - - var update = new BranchUpdate(); - update.Protection = new BranchProtection(false, requiredStatusChecks); - - var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); - - // Ensure a branch object was returned - Assert.NotNull(branch); - - // Retrieve master branch - branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); - - // Assert the branch is unprotected, and enforcement/contexts are cleared - Assert.Equal(branch.Protection.Enabled, false); - Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); - Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 0); - } - } -} \ No newline at end of file diff --git a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs index 6f65dac2ec..f6bc64a228 100644 --- a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -1370,6 +1371,12 @@ public async Task GetsAllBranchesWithRepositoryId() var branches = await github.Repository.GetAllBranches(7528679); Assert.NotEmpty(branches); + + // Ensure Protection attribute is deserialized + foreach (var branch in branches) + { + Assert.NotNull(branch.Protection); + } } [IntegrationTest] @@ -1684,4 +1691,102 @@ public async Task GetsPagesOfBranchesWithRepositoryId() Assert.NotEqual(firstPage[4].Name, secondPage[4].Name); } } + + public class TheEditBranchMethod + { + private readonly IRepositoriesClient _fixture; + private readonly RepositoryContext _context; + + public TheEditBranchMethod() + { + var github = Helper.GetAuthenticatedClient(); + _context = github.CreateRepositoryContext("source-repo").Result; + _fixture = github.Repository; + } + + public async Task CreateTheWorld() + { + // Set master branch to be protected, with some status checks + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var newBranch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + } + + [IntegrationTest] + public async Task ProtectsBranch() + { + // Set master branch to be protected, with some status checks + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2", "check3" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the changes were made + Assert.Equal(branch.Protection.Enabled, true); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Everyone); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 3); + } + + [IntegrationTest] + public async Task RemoveStatusCheckEnforcement() + { + await CreateTheWorld(); + + // Remove status check enforcement + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Off, new List { "check1" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the changes were made + Assert.Equal(branch.Protection.Enabled, true); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 1); + } + + [IntegrationTest] + public async Task UnprotectsBranch() + { + await CreateTheWorld(); + + // Unprotect branch + // Deliberately set Enforcement and Contexts to some values (these should be ignored) + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(false, requiredStatusChecks); + + var branch = await _fixture.EditBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.GetBranch(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the branch is unprotected, and enforcement/contexts are cleared + Assert.Equal(branch.Protection.Enabled, false); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 0); + } + } } diff --git a/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs new file mode 100644 index 0000000000..5202fe0090 --- /dev/null +++ b/Octokit.Tests.Integration/Clients/RepositoryBranchesClientTests.cs @@ -0,0 +1,293 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Octokit; +using Octokit.Tests.Integration; +using Octokit.Tests.Integration.Helpers; +using Xunit; + +public class RepositoryBranchesClientTests +{ + public class TheGetAllMethod + { + [IntegrationTest] + public async Task GetsAllBranches() + { + var github = Helper.GetAuthenticatedClient(); + + var branches = await github.Repository.Branch.GetAll("octokit", "octokit.net"); + + Assert.NotEmpty(branches); + + // Ensure Protection attribute is deserialized + foreach (var branch in branches) + { + Assert.NotNull(branch.Protection); + } + } + + [IntegrationTest] + public async Task GetsAllBranchesWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var branches = await github.Repository.Branch.GetAll(7528679); + + Assert.NotEmpty(branches); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfBranchesWithoutStart() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 5, + PageCount = 1 + }; + + var branches = await github.Repository.Branch.GetAll("octokit", "octokit.net", options); + + Assert.Equal(5, branches.Count); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfBranchesWithoutStartWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 5, + PageCount = 1 + }; + + var branches = await github.Repository.Branch.GetAll(7528679, options); + + Assert.Equal(5, branches.Count); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfBranchesWithStart() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 5, + PageCount = 1, + StartPage = 2 + }; + + var branches = await github.Repository.Branch.GetAll("octokit", "octokit.net", options); + + Assert.Equal(5, branches.Count); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountOfBranchesWithStartWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var options = new ApiOptions + { + PageSize = 5, + PageCount = 1, + StartPage = 2 + }; + + var branches = await github.Repository.Branch.GetAll(7528679, options); + + Assert.Equal(5, branches.Count); + } + + [IntegrationTest] + public async Task GetsPagesOfBranches() + { + var github = Helper.GetAuthenticatedClient(); + + var firstPageOptions = new ApiOptions + { + PageSize = 5, + StartPage = 1, + PageCount = 1 + }; + + var firstPage = await github.Repository.Branch.GetAll("octokit", "octokit.net", firstPageOptions); + + var secondPageOptions = new ApiOptions + { + PageSize = 5, + StartPage = 2, + PageCount = 1 + }; + + var secondPage = await github.Repository.Branch.GetAll("octokit", "octokit.net", secondPageOptions); + + Assert.Equal(5, firstPage.Count); + Assert.Equal(5, secondPage.Count); + + Assert.NotEqual(firstPage[0].Name, secondPage[0].Name); + Assert.NotEqual(firstPage[1].Name, secondPage[1].Name); + Assert.NotEqual(firstPage[2].Name, secondPage[2].Name); + Assert.NotEqual(firstPage[3].Name, secondPage[3].Name); + Assert.NotEqual(firstPage[4].Name, secondPage[4].Name); + } + + [IntegrationTest] + public async Task GetsPagesOfBranchesWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var firstPageOptions = new ApiOptions + { + PageSize = 5, + StartPage = 1, + PageCount = 1 + }; + + var firstPage = await github.Repository.Branch.GetAll(7528679, firstPageOptions); + + var secondPageOptions = new ApiOptions + { + PageSize = 5, + StartPage = 2, + PageCount = 1 + }; + + var secondPage = await github.Repository.Branch.GetAll(7528679, secondPageOptions); + + Assert.Equal(5, firstPage.Count); + Assert.Equal(5, secondPage.Count); + + Assert.NotEqual(firstPage[0].Name, secondPage[0].Name); + Assert.NotEqual(firstPage[1].Name, secondPage[1].Name); + Assert.NotEqual(firstPage[2].Name, secondPage[2].Name); + Assert.NotEqual(firstPage[3].Name, secondPage[3].Name); + Assert.NotEqual(firstPage[4].Name, secondPage[4].Name); + } + } + + public class TheGetMethod + { + [IntegrationTest] + public async Task GetsABranch() + { + var github = Helper.GetAuthenticatedClient(); + + var branch = await github.Repository.Branch.Get("octokit", "octokit.net", "master"); + + Assert.NotNull(branch); + Assert.Equal("master", branch.Name); + } + + [IntegrationTest] + public async Task GetsABranchWithRepositoryId() + { + var github = Helper.GetAuthenticatedClient(); + + var branch = await github.Repository.Branch.Get(7528679, "master"); + + Assert.NotNull(branch); + Assert.Equal("master", branch.Name); + } + } + + public class TheEditMethod + { + private readonly IRepositoryBranchesClient _fixture; + private readonly RepositoryContext _context; + + public TheEditMethod() + { + var github = Helper.GetAuthenticatedClient(); + _context = github.CreateRepositoryContext("source-repo").Result; + _fixture = github.Repository.Branch; + } + + public async Task CreateTheWorld() + { + // Set master branch to be protected, with some status checks + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var newBranch = await _fixture.Edit(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + } + + [IntegrationTest] + public async Task ProtectsBranch() + { + // Set master branch to be protected, with some status checks + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1", "check2", "check3" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var branch = await _fixture.Edit(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.Get(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the changes were made + Assert.Equal(branch.Protection.Enabled, true); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Everyone); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 3); + } + + [IntegrationTest] + public async Task RemoveStatusCheckEnforcement() + { + await CreateTheWorld(); + + // Remove status check enforcement + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Off, new List { "check1" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(true, requiredStatusChecks); + + var branch = await _fixture.Edit(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.Get(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the changes were made + Assert.Equal(branch.Protection.Enabled, true); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 1); + } + + [IntegrationTest] + public async Task UnprotectsBranch() + { + await CreateTheWorld(); + + // Unprotect branch + // Deliberately set Enforcement and Contexts to some values (these should be ignored) + var requiredStatusChecks = new RequiredStatusChecks(EnforcementLevel.Everyone, new List { "check1" }); + + var update = new BranchUpdate(); + update.Protection = new BranchProtection(false, requiredStatusChecks); + + var branch = await _fixture.Edit(_context.Repository.Owner.Login, _context.Repository.Name, "master", update); + + // Ensure a branch object was returned + Assert.NotNull(branch); + + // Retrieve master branch + branch = await _fixture.Get(_context.Repository.Owner.Login, _context.Repository.Name, "master"); + + // Assert the branch is unprotected, and enforcement/contexts are cleared + Assert.Equal(branch.Protection.Enabled, false); + Assert.Equal(branch.Protection.RequiredStatusChecks.EnforcementLevel, EnforcementLevel.Off); + Assert.Equal(branch.Protection.RequiredStatusChecks.Contexts.Count, 0); + } + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 62a1cd4090..4c8ae8b5f5 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -76,7 +76,6 @@ - @@ -104,6 +103,7 @@ + diff --git a/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs b/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs new file mode 100644 index 0000000000..f9164c4b34 --- /dev/null +++ b/Octokit.Tests/Clients/RepositoryBranchesClientTests.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using NSubstitute; +using Xunit; + +namespace Octokit.Tests.Clients +{ + /// + /// Client tests mostly just need to make sure they call the IApiConnection with the correct + /// relative Uri. No need to fake up the response. All *those* tests are in ApiConnectionTests.cs. + /// + public class RepositoryBranchesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new RepositoryBranchesClient(null)); + } + } + + public class TheGetAllMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + await client.GetAll("owner", "name"); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "repos/owner/name/branches"), null, "application/vnd.github.loki-preview+json", Args.ApiOptions); + } + + [Fact] + public async Task RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + await client.GetAll(1); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "repositories/1/branches"), null, "application/vnd.github.loki-preview+json", Args.ApiOptions); + } + + [Fact] + public async Task RequestsTheCorrectUrlWithApiOptions() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + var options = new ApiOptions + { + PageCount = 1, + StartPage = 1, + PageSize = 1 + }; + + await client.GetAll("owner", "name", options); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "repos/owner/name/branches"), null, "application/vnd.github.loki-preview+json", options); + } + + [Fact] + public async Task RequestsTheCorrectUrlWithRepositoryIdWithApiOptions() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + var options = new ApiOptions + { + PageCount = 1, + StartPage = 1, + PageSize = 1 + }; + + await client.GetAll(1, options); + + connection.Received() + .GetAll(Arg.Is(u => u.ToString() == "repositories/1/branches"), null, "application/vnd.github.loki-preview+json", options); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null, "name")); + await Assert.ThrowsAsync(() => client.GetAll("owner", null)); + + await Assert.ThrowsAsync(() => client.GetAll(null, "name", ApiOptions.None)); + await Assert.ThrowsAsync(() => client.GetAll("owner", null, ApiOptions.None)); + await Assert.ThrowsAsync(() => client.GetAll("owner", "name", null)); + + await Assert.ThrowsAsync(() => client.GetAll(1, null)); + + await Assert.ThrowsAsync(() => client.GetAll("", "name")); + await Assert.ThrowsAsync(() => client.GetAll("owner", "")); + await Assert.ThrowsAsync(() => client.GetAll("", "name", ApiOptions.None)); + await Assert.ThrowsAsync(() => client.GetAll("owner", "", ApiOptions.None)); + } + } + + public class TheGetMethod + { + [Fact] + public async Task RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + await client.Get("owner", "repo", "branch"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repos/owner/repo/branches/branch"), null, "application/vnd.github.loki-preview+json"); + } + + [Fact] + public async Task RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + + await client.Get(1, "branch"); + + connection.Received() + .Get(Arg.Is(u => u.ToString() == "repositories/1/branches/branch"), null, "application/vnd.github.loki-preview+json"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Get(null, "repo", "branch")); + await Assert.ThrowsAsync(() => client.Get("owner", null, "branch")); + await Assert.ThrowsAsync(() => client.Get("owner", "repo", null)); + + await Assert.ThrowsAsync(() => client.Get(1, null)); + + await Assert.ThrowsAsync(() => client.Get("", "repo", "branch")); + await Assert.ThrowsAsync(() => client.Get("owner", "", "branch")); + await Assert.ThrowsAsync(() => client.Get("owner", "repo", "")); + } + } + + public class TheEditMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + var update = new BranchUpdate(); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.Edit("owner", "repo", "branch", update); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "repos/owner/repo/branches/branch"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var connection = Substitute.For(); + var client = new RepositoryBranchesClient(connection); + var update = new BranchUpdate(); + const string previewAcceptsHeader = "application/vnd.github.loki-preview+json"; + + client.Edit(1, "branch", update); + + connection.Received() + .Patch(Arg.Is(u => u.ToString() == "repositories/1/branches/branch"), Arg.Any(), previewAcceptsHeader); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new RepositoryBranchesClient(Substitute.For()); + var update = new BranchUpdate(); + + await Assert.ThrowsAsync(() => client.Edit(null, "repo", "branch", update)); + await Assert.ThrowsAsync(() => client.Edit("owner", null, "branch", update)); + await Assert.ThrowsAsync(() => client.Edit("owner", "repo", null, update)); + await Assert.ThrowsAsync(() => client.Edit("owner", "repo", "branch", null)); + + await Assert.ThrowsAsync(() => client.Edit(1, null, update)); + await Assert.ThrowsAsync(() => client.Edit(1, "branch", null)); + + await Assert.ThrowsAsync(() => client.Edit("", "repo", "branch", update)); + await Assert.ThrowsAsync(() => client.Edit("owner", "", "branch", update)); + await Assert.ThrowsAsync(() => client.Edit("owner", "repo", "", update)); + + await Assert.ThrowsAsync(() => client.Edit(1, "", update)); + } + } + } +} diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index 673961abfb..7523d23771 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -99,6 +99,7 @@ + @@ -239,6 +240,7 @@ + diff --git a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs index 51ff60fa15..e98e03dda8 100644 --- a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs @@ -848,7 +848,7 @@ public void RequestsTheCorrectUrl() client.GetBranch("owner", "repo", "branch"); - github.Repository.Received(1).GetBranch("owner", "repo", "branch"); + github.Repository.Branch.Received(1).Get("owner", "repo", "branch"); } [Fact] @@ -859,7 +859,7 @@ public void RequestsTheCorrectUrlWithRepositoryId() client.GetBranch(1, "branch"); - github.Repository.Received(1).GetBranch(1, "branch"); + github.Repository.Branch.Received(1).Get(1, "branch"); } [Fact] @@ -935,7 +935,7 @@ public void PatchsTheCorrectUrl() client.EditBranch("owner", "repo", "branch", update); - github.Repository.Received(1).EditBranch("owner", "repo", "branch", update); + github.Repository.Branch.Received(1).Edit("owner", "repo", "branch", update); } [Fact] @@ -947,7 +947,7 @@ public void PatchsTheCorrectUrlWithRepositoryId() client.EditBranch(1, "branch", update); - github.Repository.Received(1).EditBranch(1, "branch", update); + github.Repository.Branch.Received(1).Edit(1, "branch", update); } [Fact] diff --git a/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs new file mode 100644 index 0000000000..91811fd1b8 --- /dev/null +++ b/Octokit.Tests/Reactive/ObservableRepositoryBranchesClientTests.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Linq; +using System.Threading.Tasks; +using NSubstitute; +using Octokit.Internal; +using Octokit.Reactive; +using Xunit; + +namespace Octokit.Tests.Reactive +{ + public class ObservableRepositoryBranchesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws( + () => new ObservableRepositoryBranchesClient(null)); + } + } + + public class TheGetAllMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var expected = new Uri("repos/owner/repo/branches", UriKind.Relative); + + client.GetAll("owner", "repo"); + + gitHubClient.Connection.Received(1).Get>(expected, Args.EmptyDictionary, null); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var expected = new Uri("repositories/1/branches", UriKind.Relative); + + client.GetAll(1); + + gitHubClient.Connection.Received(1).Get>(expected, Args.EmptyDictionary, null); + } + + [Fact] + public void RequestsTheCorrectUrlWithApiOptions() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var expected = new Uri("repos/owner/name/branches", UriKind.Relative); + + var options = new ApiOptions + { + PageCount = 1, + StartPage = 1, + PageSize = 1 + }; + + client.GetAll("owner", "name", options); + + gitHubClient.Connection.Received(1).Get>(expected, Arg.Is>(d => d.Count == 2 && d["page"] == "1" && d["per_page"] == "1"), null); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryIdWithApiOptions() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(gitHubClient); + var expected = new Uri("repositories/1/branches", UriKind.Relative); + + var options = new ApiOptions + { + PageCount = 1, + StartPage = 1, + PageSize = 1 + }; + + client.GetAll(1, options); + + gitHubClient.Connection.Received(1).Get>(expected, Arg.Is>(d => d.Count == 2 && d["page"] == "1" && d["per_page"] == "1"), null); + } + + [Fact] + public void EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + + Assert.Throws(() => client.GetAll(null, "name")); + Assert.Throws(() => client.GetAll("owner", null)); + + Assert.Throws(() => client.GetAll(null, "name", ApiOptions.None)); + Assert.Throws(() => client.GetAll("owner", null, ApiOptions.None)); + Assert.Throws(() => client.GetAll("owner", "name", null)); + + Assert.Throws(() => client.GetAll(1, null)); + + Assert.Throws(() => client.GetAll("", "name")); + Assert.Throws(() => client.GetAll("owner", "")); + Assert.Throws(() => client.GetAll("", "name", ApiOptions.None)); + Assert.Throws(() => client.GetAll("owner", "", ApiOptions.None)); + } + } + + public class TheGetMethod + { + [Fact] + public void RequestsTheCorrectUrl() + { + var github = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(github); + + client.Get("owner", "repo", "branch"); + + github.Repository.Branch.Received(1).Get("owner", "repo", "branch"); + } + + [Fact] + public void RequestsTheCorrectUrlWithRepositoryId() + { + var github = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(github); + + client.Get(1, "branch"); + + github.Repository.Branch.Received(1).Get(1, "branch"); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + + Assert.Throws(() => client.Get(null, "repo", "branch")); + Assert.Throws(() => client.Get("owner", null, "branch")); + Assert.Throws(() => client.Get("owner", "repo", null)); + + Assert.Throws(() => client.Get(1, null)); + + Assert.Throws(() => client.Get("", "repo", "branch")); + Assert.Throws(() => client.Get("owner", "", "branch")); + Assert.Throws(() => client.Get("owner", "repo", "")); + + Assert.Throws(() => client.Get(1, "")); + } + } + + public class TheEditMethod + { + [Fact] + public void PatchsTheCorrectUrl() + { + var github = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(github); + var update = new BranchUpdate(); + + client.Edit("owner", "repo", "branch", update); + + github.Repository.Branch.Received(1).Edit("owner", "repo", "branch", update); + } + + [Fact] + public void PatchsTheCorrectUrlWithRepositoryId() + { + var github = Substitute.For(); + var client = new ObservableRepositoryBranchesClient(github); + var update = new BranchUpdate(); + + client.Edit(1, "branch", update); + + github.Repository.Branch.Received(1).Edit(1, "branch", update); + } + + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new ObservableRepositoryBranchesClient(Substitute.For()); + var update = new BranchUpdate(); + + Assert.Throws(() => client.Edit(null, "repo", "branch", update)); + Assert.Throws(() => client.Edit("owner", null, "branch", update)); + Assert.Throws(() => client.Edit("owner", "repo", null, update)); + Assert.Throws(() => client.Edit("owner", "repo", "branch", null)); + + Assert.Throws(() => client.Edit(1, null, update)); + Assert.Throws(() => client.Edit(1, "branch", null)); + + Assert.Throws(() => client.Edit("", "repo", "branch", update)); + Assert.Throws(() => client.Edit("owner", "", "branch", update)); + Assert.Throws(() => client.Edit("owner", "repo", "", update)); + + Assert.Throws(() => client.Edit(1, "", update)); + } + } + } +} diff --git a/Octokit/Clients/IRepositoriesClient.cs b/Octokit/Clients/IRepositoriesClient.cs index 469f152c59..be88eedf9c 100644 --- a/Octokit/Clients/IRepositoriesClient.cs +++ b/Octokit/Clients/IRepositoriesClient.cs @@ -23,6 +23,15 @@ public interface IRepositoriesClient /// IPullRequestsClient PullRequest { get; } + /// + /// Client for managing branches in a repository. + /// + /// + /// See the Branches API documentation for more details + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + IRepositoryBranchesClient Branch { get; } + /// /// Client for managing commit comments in a repository. /// @@ -325,6 +334,7 @@ public interface IRepositoriesClient /// The name of the repository /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] Task> GetAllBranches(string owner, string name); /// @@ -336,6 +346,7 @@ public interface IRepositoriesClient /// The Id of the repository /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] Task> GetAllBranches(int repositoryId); /// @@ -349,6 +360,7 @@ public interface IRepositoriesClient /// Options for changing the API response /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] Task> GetAllBranches(string owner, string name, ApiOptions options); /// @@ -361,6 +373,7 @@ public interface IRepositoriesClient /// Options for changing the API response /// Thrown when a general API error occurs. /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] Task> GetAllBranches(int repositoryId, ApiOptions options); /// @@ -574,6 +587,7 @@ public interface IRepositoriesClient /// The name of the repository /// The name of the branch /// The specified + [Obsolete("Please use RepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] Task GetBranch(string owner, string name, string branchName); /// @@ -585,6 +599,7 @@ public interface IRepositoriesClient /// The Id of the repository /// The name of the branch /// The specified + [Obsolete("Please use RepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] Task GetBranch(int repositoryId, string branchName); /// @@ -612,7 +627,7 @@ public interface IRepositoriesClient /// The name of the branch /// New values to update the branch with /// The updated - [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + [Obsolete("Please use RepositoriesClient.Branch.Edit() instead. This method will be removed in a future version")] Task EditBranch(string owner, string name, string branch, BranchUpdate update); /// @@ -622,7 +637,7 @@ public interface IRepositoriesClient /// The name of the branch /// New values to update the branch with /// The updated - [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + [Obsolete("Please use RepositoriesClient.Branch.Edit() instead. This method will be removed in a future version")] Task EditBranch(int repositoryId, string branch, BranchUpdate update); /// diff --git a/Octokit/Clients/IRepositoryBranchesClient.cs b/Octokit/Clients/IRepositoryBranchesClient.cs new file mode 100644 index 0000000000..fdccd556e1 --- /dev/null +++ b/Octokit/Clients/IRepositoryBranchesClient.cs @@ -0,0 +1,101 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using System.Collections.ObjectModel; +#if NET_45 +using System.Collections.Generic; +#endif + +namespace Octokit +{ + /// + /// A client for GitHub's Repository Branches API. + /// + /// + /// See the Repository Branches API documentation for more details. + /// + public interface IRepositoryBranchesClient + { + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + Task> GetAll(string owner, string name); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + Task> GetAll(int repositoryId); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Options for changing the API response + Task> GetAll(string owner, string name, ApiOptions options); + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// Options for changing the API response + Task> GetAll(int repositoryId, ApiOptions options); + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + Task Get(string owner, string name, string branch); + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The ID of the repository + /// The name of the branch + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] + Task Get(int repositoryId, string branch); + + /// + /// Edit the specified branch with the values given in + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + Task Edit(string owner, string name, string branch, BranchUpdate update); + + /// + /// Edit the specified branch with the values given in + /// + /// The Id of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + Task Edit(int repositoryId, string branch, BranchUpdate update); + } +} diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index dbb0fd095a..eefa7e5441 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; #if NET_45 using System.Collections.Generic; #endif @@ -37,6 +38,7 @@ public RepositoriesClient(IApiConnection apiConnection) : base(apiConnection) Content = new RepositoryContentsClient(apiConnection); Page = new RepositoryPagesClient(apiConnection); Invitation = new RepositoryInvitationsClient(apiConnection); + Branch = new RepositoryBranchesClient(apiConnection); } /// @@ -165,16 +167,16 @@ public Task Delete(int repositoryId) /// Gets the specified branch. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository /// The name of the branch - /// The specified + [Obsolete("Please use RepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] public Task GetBranch(int repositoryId, string branchName) { Ensure.ArgumentNotNullOrEmptyString(branchName, "branchName"); - return ApiConnection.Get(ApiUrls.RepoBranch(repositoryId, branchName), null, AcceptHeaders.ProtectedBranchesApiPreview); + return Branch.Get(repositoryId, branchName); } /// @@ -222,7 +224,7 @@ public Task EditBranch(string owner, string name, string branch, BranchU Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); Ensure.ArgumentNotNull(update, "update"); - return ApiConnection.Patch(ApiUrls.RepoBranch(owner, name, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + return Branch.Edit(owner, name, branch, update); } /// @@ -232,13 +234,13 @@ public Task EditBranch(string owner, string name, string branch, BranchU /// The name of the branch /// New values to update the branch with /// The updated - [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + [Obsolete("Please use RepositoriesClient.Branch.Edit() instead. This method will be removed in a future version")] public Task EditBranch(int repositoryId, string branch, BranchUpdate update) { Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); Ensure.ArgumentNotNull(update, "update"); - return ApiConnection.Patch(ApiUrls.RepoBranch(repositoryId, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + return Branch.Edit(repositoryId, branch, update); } /// @@ -435,6 +437,15 @@ public Task> GetAllForOrg(string organization, ApiOpti return ApiConnection.GetAll(ApiUrls.OrganizationRepositories(organization), options); } + /// + /// A client for GitHub's Repository Branches API. + /// + /// + /// See the Branches API documentation for more details + /// + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] + public IRepositoryBranchesClient Branch { get; private set; } + /// /// A client for GitHub's Commit Status API. /// @@ -541,69 +552,65 @@ public Task> GetAllForOrg(string organization, ApiOpti /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository - /// Thrown when a general API error occurs. - /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public Task> GetAllBranches(string owner, string name) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); - return GetAllBranches(owner, name, ApiOptions.None); + return Branch.GetAll(owner, name); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository - /// Thrown when a general API error occurs. - /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public Task> GetAllBranches(int repositoryId) { - return GetAllBranches(repositoryId, ApiOptions.None); + return Branch.GetAll(repositoryId); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository /// Options for changing the API response - /// Thrown when a general API error occurs. - /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public Task> GetAllBranches(string owner, string name, ApiOptions options) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.RepoBranches(owner, name), null, AcceptHeaders.ProtectedBranchesApiPreview, options); + return Branch.GetAll(owner, name, options); } /// /// Gets all the branches for the specified repository. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The Id of the repository /// Options for changing the API response - /// Thrown when a general API error occurs. - /// All es of the repository + [Obsolete("Please use RepositoriesClient.Branch.GetAll() instead. This method will be removed in a future version")] public Task> GetAllBranches(int repositoryId, ApiOptions options) { Ensure.ArgumentNotNull(options, "options"); - return ApiConnection.GetAll(ApiUrls.RepoBranches(repositoryId), null, AcceptHeaders.ProtectedBranchesApiPreview, options); + return Branch.GetAll(repositoryId, options); } /// @@ -920,19 +927,19 @@ public Task> GetAllTags(int repositoryId, ApiOption /// Gets the specified branch. /// /// - /// See the API documentation for more details + /// See the API documentation for more details /// /// The owner of the repository /// The name of the repository /// The name of the branch - /// The specified + [Obsolete("Please use RepositoriesClient.Branch.Get() instead. This method will be removed in a future version")] public Task GetBranch(string owner, string name, string branchName) { Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(branchName, "branchName"); - return ApiConnection.Get(ApiUrls.RepoBranch(owner, name, branchName), null, AcceptHeaders.ProtectedBranchesApiPreview); + return Branch.Get(owner, name, branchName); } /// diff --git a/Octokit/Clients/RepositoryBranchesClient.cs b/Octokit/Clients/RepositoryBranchesClient.cs new file mode 100644 index 0000000000..acce61afc2 --- /dev/null +++ b/Octokit/Clients/RepositoryBranchesClient.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Octokit +{ + /// + /// A client for GitHub's Repository Branches API. + /// + /// + /// See the Repository Branches API documentation for more details. + /// + public class RepositoryBranchesClient : ApiClient, IRepositoryBranchesClient + { + /// + /// Initializes a new GitHub Repository Branches API client. + /// + /// An API connection + public RepositoryBranchesClient(IApiConnection apiConnection) + : base(apiConnection) + { + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + public Task> GetAll(string owner, string name) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return GetAll(owner, name, ApiOptions.None); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + public Task> GetAll(int repositoryId) + { + return GetAll(repositoryId, ApiOptions.None); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// Options for changing the API response + /// Thrown when a general API error occurs. + public Task> GetAll(string owner, string name, ApiOptions options) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(options, "options"); + + return ApiConnection.GetAll(ApiUrls.RepoBranches(owner, name), null, AcceptHeaders.ProtectedBranchesApiPreview, options); + } + + /// + /// Gets all the branches for the specified repository. + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// Options for changing the API response + public Task> GetAll(int repositoryId, ApiOptions options) + { + Ensure.ArgumentNotNull(options, "options"); + + return ApiConnection.GetAll(ApiUrls.RepoBranches(repositoryId), null, AcceptHeaders.ProtectedBranchesApiPreview, options); + } + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + public Task Get(string owner, string name, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + + return ApiConnection.Get(ApiUrls.RepoBranch(owner, name, branch), null, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Gets the specified branch. + /// + /// + /// See the API documentation for more details + /// + /// The Id of the repository + /// The name of the branch + public Task Get(int repositoryId, string branch) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branchName"); + + return ApiConnection.Get(ApiUrls.RepoBranch(repositoryId, branch), null, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Edit the specified branch with the values given in + /// + /// The owner of the repository + /// The name of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + public Task Edit(string owner, string name, string branch, BranchUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(owner, "owner"); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, "update"); + + return ApiConnection.Patch(ApiUrls.RepoBranch(owner, name, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + } + + /// + /// Edit the specified branch with the values given in + /// + /// The Id of the repository + /// The name of the branch + /// New values to update the branch with + [Obsolete("BranchProtection preview functionality in the GitHub API has had breaking changes. This existing implementation will cease to work when the preview period ends.")] + public Task Edit(int repositoryId, string branch, BranchUpdate update) + { + Ensure.ArgumentNotNullOrEmptyString(branch, "branch"); + Ensure.ArgumentNotNull(update, "update"); + + return ApiConnection.Patch(ApiUrls.RepoBranch(repositoryId, branch), update, AcceptHeaders.ProtectedBranchesApiPreview); + } + } +} diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 6671612640..06fe2de9e0 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -489,6 +489,8 @@ + + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index 3b494a38e5..2f1544ef68 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -500,6 +500,8 @@ + + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 40f8ba1911..d78c107573 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -496,6 +496,8 @@ + + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 3270a8efa6..17048e23bc 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -486,6 +486,8 @@ + + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index c0b3f04f29..6529d0aced 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -493,6 +493,8 @@ + + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 0e0f5347c4..fd8d6e446f 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -64,6 +64,7 @@ + @@ -98,6 +99,7 @@ +