Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Protected Branches API support #996

Merged
merged 17 commits into from
Dec 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Octokit.Reactive/Clients/IObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@ public interface IObservableRepositoriesClient
/// <returns>The updated <see cref="T:Octokit.Repository"/></returns>
IObservable<Repository> Edit(string owner, string name, RepositoryUpdate update);

/// <summary>
/// Edit the specified branch with the values given in <paramref name="update"/>
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="branch">The name of the branch</param>
/// <param name="update">New values to update the branch with</param>
/// <returns>The updated <see cref="T:Octokit.Branch"/></returns>
IObservable<Branch> EditBranch(string owner, string name, string branch, BranchUpdate update);

/// <summary>
/// A client for GitHub's Repo Collaborators.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,19 @@ public IObservable<Repository> Edit(string owner, string name, RepositoryUpdate
return _client.Edit(owner, name, update).ToObservable();
}

/// <summary>
/// Edit the specified branch with the values given in <paramref name="update"/>
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="branch">The name of the branch</param>
/// <param name="update">New values to update the branch with</param>
/// <returns>The updated <see cref="T:Octokit.Branch"/></returns>
public IObservable<Branch> EditBranch(string owner, string name, string branch, BranchUpdate update)
{
return _client.EditBranch(owner, name, branch, update).ToObservable();
}

/// <summary>
/// Compare two references in a repository
/// </summary>
Expand Down
100 changes: 100 additions & 0 deletions Octokit.Tests.Integration/Clients/BranchesClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Octokit;
using Octokit.Tests.Integration;
Expand All @@ -22,7 +23,106 @@ public async Task ReturnsBranches()

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<string>() { "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<string>() { "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<string>() { "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<string>() { "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);
}
}
}
36 changes: 34 additions & 2 deletions Octokit.Tests/Clients/RepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ public void ReturnsBranches()
client.GetAllBranches("owner", "name");

connection.Received()
.GetAll<Branch>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/branches"));
.GetAll<Branch>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/name/branches"), null, "application/vnd.github.loki-preview+json");
}

[Fact]
Expand Down Expand Up @@ -565,7 +565,7 @@ public void GetsCorrectUrl()
client.GetBranch("owner", "repo", "branch");

connection.Received()
.Get<Branch>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo/branches/branch"), null);
.Get<Branch>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo/branches/branch"), null, "application/vnd.github.loki-preview+json");
}

[Fact]
Expand Down Expand Up @@ -717,5 +717,37 @@ public void GetsCorrectUrl()
Arg.Any<Dictionary<string, string>>());
}
}

public class TheEditBranchMethod
{
[Fact]
public void GetsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoriesClient(connection);
var update = new BranchUpdate();
const string previewAcceptsHeader = "application/vnd.github.loki-preview+json";

client.EditBranch("owner", "repo", "branch", update);

connection.Received()
.Patch<Branch>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo/branches/branch"), Arg.Any<BranchUpdate>(), previewAcceptsHeader);
}

[Fact]
public async Task EnsuresNonNullArguments()
{
var client = new RepositoriesClient(Substitute.For<IApiConnection>());
var update = new BranchUpdate();

await Assert.ThrowsAsync<ArgumentNullException>(() => client.EditBranch(null, "repo", "branch", update));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.EditBranch("owner", null, "branch", update));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.EditBranch("owner", "repo", null, update));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.EditBranch("owner", "repo", "branch", null));
await Assert.ThrowsAsync<ArgumentException>(() => client.EditBranch("", "repo", "branch", update));
await Assert.ThrowsAsync<ArgumentException>(() => client.EditBranch("owner", "", "branch", update));
await Assert.ThrowsAsync<ArgumentException>(() => client.EditBranch("owner", "repo", "", update));
}
}
}
}
33 changes: 33 additions & 0 deletions Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,39 @@ public void CallsIntoClient()
}
}

public class TheEditBranchMethod
{
[Fact]
public async Task EnsuresArguments()
{
var github = Substitute.For<IGitHubClient>();
var nonreactiveClient = new RepositoriesClient(Substitute.For<IApiConnection>());
github.Repository.Returns(nonreactiveClient);
var client = new ObservableRepositoriesClient(github);
var update = new BranchUpdate();

Assert.Throws<ArgumentNullException>(() => client.EditBranch(null, "repo", "branch", update));
Assert.Throws<ArgumentNullException>(() => client.EditBranch("owner", null, "branch", update));
Assert.Throws<ArgumentNullException>(() => client.EditBranch("owner", "repo", null, update));
Assert.Throws<ArgumentNullException>(() => client.EditBranch("owner", "repo", "branch", null));
Assert.Throws<ArgumentException>(() => client.EditBranch("", "repo", "branch", update));
Assert.Throws<ArgumentException>(() => client.EditBranch("owner", "", "branch", update));
Assert.Throws<ArgumentException>(() => client.EditBranch("owner", "repo", "", update));
}

[Fact]
public void CallsIntoClient()
{
var github = Substitute.For<IGitHubClient>();
var client = new ObservableRepositoriesClient(github);
var update = new BranchUpdate();

client.EditBranch("owner", "repo", "branch", update);

github.Repository.Received(1).EditBranch("owner", "repo", "branch", update);
}
}

static IResponse CreateResponseWithApiInfo(IDictionary<string, Uri> links)
{
var response = Substitute.For<IResponse>();
Expand Down
10 changes: 10 additions & 0 deletions Octokit/Clients/IRepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,5 +327,15 @@ public interface IRepositoriesClient
/// <param name="update">New values to update the repository with</param>
/// <returns>The updated <see cref="T:Octokit.Repository"/></returns>
Task<Repository> Edit(string owner, string name, RepositoryUpdate update);

/// <summary>
/// Edit the specified branch with the values given in <paramref name="update"/>
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="branch">The name of the branch</param>
/// <param name="update">New values to update the branch with</param>
/// <returns>The updated <see cref="T:Octokit.Branch"/></returns>
Task<Branch> EditBranch(string owner, string name, string branch, BranchUpdate update);
}
}
23 changes: 20 additions & 3 deletions Octokit/Clients/RepositoriesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,24 @@ public Task<Repository> Edit(string owner, string name, RepositoryUpdate update)
return ApiConnection.Patch<Repository>(ApiUrls.Repository(owner, name), update);
}

/// <summary>
/// Edit the specified branch with the values given in <paramref name="update"/>
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="branch">The name of the branch</param>
/// <param name="update">New values to update the branch with</param>
/// <returns>The updated <see cref="T:Octokit.Branch"/></returns>
public Task<Branch> EditBranch(string owner, string name, string branch, BranchUpdate update)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "repositoryName");
Ensure.ArgumentNotNullOrEmptyString(branch, "branchName");
Ensure.ArgumentNotNull(update, "update");

return ApiConnection.Patch<Branch>(ApiUrls.RepoBranch(owner, name, branch), update, AcceptHeaders.ProtectedBranchesApiPreview);
}

/// <summary>
/// Gets the specified repository.
/// </summary>
Expand Down Expand Up @@ -389,8 +407,7 @@ public Task<IReadOnlyList<Branch>> GetAllBranches(string owner, string name)
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");

var endpoint = ApiUrls.RepoBranches(owner, name);
return ApiConnection.GetAll<Branch>(endpoint);
return ApiConnection.GetAll<Branch>(ApiUrls.RepoBranches(owner, name), null, AcceptHeaders.ProtectedBranchesApiPreview);
}


Expand Down Expand Up @@ -502,7 +519,7 @@ public Task<Branch> GetBranch(string owner, string repositoryName, string branch
Ensure.ArgumentNotNullOrEmptyString(repositoryName, "repositoryName");
Ensure.ArgumentNotNullOrEmptyString(branchName, "branchName");

return ApiConnection.Get<Branch>(ApiUrls.RepoBranch(owner, repositoryName, branchName));
return ApiConnection.Get<Branch>(ApiUrls.RepoBranch(owner, repositoryName, branchName), null, AcceptHeaders.ProtectedBranchesApiPreview);
}
}
}
8 changes: 8 additions & 0 deletions Octokit/Helpers/AcceptHeaders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Octokit
{
public static class AcceptHeaders
{
public const string ProtectedBranchesApiPreview = "application/vnd.github.loki-preview+json";
}
}

27 changes: 27 additions & 0 deletions Octokit/Models/Request/BranchUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Diagnostics;
using System.Globalization;

namespace Octokit
{
/// <summary>
/// Specifies the values used to update a <see cref="Branch"/>.
/// Note: this is a PREVIEW api: https://developer.github.com/changes/2015-11-11-protected-branches-api/
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class BranchUpdate
{
/// <summary>
/// The <see cref="BranchProtection"/> details
/// </summary>
public BranchProtection Protection { get; set; }

internal string DebuggerDisplay
{
get
{
return String.Format(CultureInfo.InvariantCulture, "Protection: {0}", Protection.DebuggerDisplay);
}
}
}
}
9 changes: 8 additions & 1 deletion Octokit/Models/Response/Branch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@ public class Branch
{
public Branch() { }

public Branch(string name, GitReference commit)
public Branch(string name, GitReference commit, BranchProtection protection)
{
Name = name;
Commit = commit;
Protection = protection;
}

/// <summary>
/// Name of this <see cref="Branch"/>.
/// </summary>
public string Name { get; protected set; }

/// <summary>
/// The <see cref="BranchProtection"/> details for this <see cref="Branch"/>.
/// Note: this is a PREVIEW api: https://developer.github.com/changes/2015-11-11-protected-branches-api/
/// </summary>
public BranchProtection Protection { get; protected set; }

/// <summary>
/// The <see cref="GitReference"/> history for this <see cref="Branch"/>.
/// </summary>
Expand Down
Loading