Merge branch 'master' into release-0.6

Conflicts:
	Octokit.Tests.Integration/Clients/MiscellaneousClientTests.cs
	build.cmd
	build.fsx
This commit is contained in:
Brendan Forster
2014-12-10 20:15:06 +10:30
34 changed files with 635 additions and 152 deletions

1
.gitignore vendored
View File

@@ -69,4 +69,5 @@ nunit-*.xml
*.userprefs
packaging/
tools/FAKE.Core
tools/SourceLink.Fake
*.ncrunch*

View File

@@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Octokit;
using Octokit.Tests.Helpers;
using Octokit.Tests.Integration;
using Xunit;
@@ -54,7 +55,7 @@ public class GistsClientTests
Assert.NotNull(updatedGist);
Assert.Equal(updatedGist.Description, gistUpdate.Description);
Assert.DoesNotThrow(async () => { await _fixture.Delete(createdGist.Id); });
await _fixture.Delete(createdGist.Id);
}
[IntegrationTest]
@@ -103,15 +104,8 @@ public class GistsClientTests
Assert.True(gists.Count > 0);
// Make sure we can successfully request gists for another user
Assert.DoesNotThrow(async () => { await _fixture.GetAllForUser("FakeHaacked"); });
Assert.DoesNotThrow(async () => { await _fixture.GetAllForUser("FakeHaacked", startTime); });
// Test public gists
var publicGists = await _fixture.GetAllPublic();
Assert.True(publicGists.Count > 1);
var publicGistsSinceStartTime = await _fixture.GetAllPublic(startTime);
Assert.True(publicGistsSinceStartTime.Count > 0);
await _fixture.GetAllForUser("FakeHaacked");
await _fixture.GetAllForUser("FakeHaacked", startTime);
// Test starred gists
await _fixture.Star(createdGist.Id);

View File

@@ -31,9 +31,10 @@ public class MiscellaneousClientTests
Credentials = Helper.Credentials
};
var result = await github.Miscellaneous.RenderRawMarkdown("This is a **test**");
var result = await github.Miscellaneous.RenderRawMarkdown("This is\r\n a **test**");
Assert.Equal("<p>This is a <strong>test</strong></p>\n", result);
//Assert.Equal("<p>This is\n a <strong>test</strong></p>\n", result);
}
}
}

View File

@@ -96,7 +96,7 @@ public class PullRequestReviewCommentsClientTests : IDisposable
var createdComment = await CreateComment(body, position, pullRequest.Sha, pullRequest.Number);
Assert.DoesNotThrow(async () => { await _client.Delete(Helper.UserName, _repository.Name, createdComment.Id); });
await _client.Delete(Helper.UserName, _repository.Name, createdComment.Id);
}
[IntegrationTest]

View File

@@ -501,7 +501,7 @@ public class RepositoriesClientTests
var repoName = Helper.MakeNameWithTimestamp("repo-to-delete");
await github.Repository.Create(new NewRepository { Name = repoName });
Assert.DoesNotThrow(async () => { await github.Repository.Delete(Helper.UserName, repoName); });
await github.Repository.Delete(Helper.UserName, repoName);
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
using Octokit;
@@ -33,6 +34,14 @@ public class RepositoryCommitsClientTests : IDisposable
Assert.NotNull(commit);
}
[IntegrationTest]
public async Task CanGetCommitWithFiles()
{
var commit = await _fixture.Get("octokit", "octokit.net", "65a22f4d2cff94a286ac3e96440c810c5509196f");
Assert.True(commit.Files.Any(file => file.Filename.EndsWith("IConnection.cs")));
}
[IntegrationTest]
public async Task CanGetListOfCommits()
{

View File

@@ -101,6 +101,7 @@
<Compile Include="Reactive\ObservableRespositoryDeployKeysClientTests.cs" />
<Compile Include="Reactive\ObservableUserEmailsClientTests.cs" />
<Compile Include="Reactive\ObservableTeamsClientTests.cs" />
<Compile Include="SelfTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Octokit.Reactive\Octokit.Reactive.csproj">

View File

@@ -0,0 +1,17 @@
using Xunit;
namespace Octokit.Tests.Integration
{
/// <summary>
/// Tests to make sure our tests are ok.
/// </summary>
public class SelfTests
{
[Fact]
public void NoTestsUseAsyncVoid()
{
var errors = typeof(SelfTests).Assembly.GetAsyncVoidMethodsList();
Assert.Equal("", errors);
}
}
}

View File

@@ -195,14 +195,15 @@ namespace Octokit.Tests.Clients
public class TheCommitsMethod
{
[Fact]
public async void RequestsCorrectUrl()
public async Task RequestsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new PullRequestsClient(connection);
client.Commits("fake", "repo", 42);
await client.Commits("fake", "repo", 42);
connection.Received().GetAll<PullRequestCommit>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/pulls/42/commits"));
connection.Received()
.GetAll<PullRequestCommit>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/pulls/42/commits"));
}
[Fact]

View File

@@ -1,9 +1,7 @@
using System;
using System.Threading.Tasks;
using NSubstitute;
using Octokit.Tests.Helpers;
using Xunit;
using System.Collections.Generic;
using NSubstitute;
using Xunit;
namespace Octokit.Tests.Clients
{
@@ -336,7 +334,8 @@ namespace Octokit.Tests.Clients
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
client.SearchRepo(new SearchRepositoriesRequest("something"));
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Any<Dictionary<string, string>>());
}
[Fact]
@@ -351,13 +350,12 @@ namespace Octokit.Tests.Clients
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//check sizes for repos that are greater than 50 MB
var request = new SearchRepositoriesRequest("github");
request.Size = Range.GreaterThan(50);
request.Size = Range.GreaterThan(1);
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+size:>1"));
}
[Fact]
@@ -368,10 +366,38 @@ namespace Octokit.Tests.Clients
//get repos whos stargazers are greater than 500
var request = new SearchRepositoriesRequest("github");
request.Stars = Range.GreaterThan(500);
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+stars:>500"));
}
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
[Fact]
public void TestingTheStarsQualifier_LessThan()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos whos stargazers are less than 500
var request = new SearchRepositoriesRequest("github");
request.Stars = Range.LessThan(500);
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+stars:<500"));
}
[Fact]
public void TestingTheStarsQualifier_LessThanOrEquals()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos whos stargazers are less than 500 or equal to
var request = new SearchRepositoriesRequest("github");
request.Stars = Range.LessThanOrEquals(500);
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+stars:<=500"));
}
[Fact]
@@ -382,10 +408,9 @@ namespace Octokit.Tests.Clients
//get repos which has forks that are greater than 50
var request = new SearchRepositoriesRequest("github");
request.Forks = Range.GreaterThan(50);
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+forks:>50"));
}
[Fact]
@@ -394,12 +419,11 @@ namespace Octokit.Tests.Clients
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//search repos that contains rails and forks are included in the search
var request = new SearchRepositoriesRequest("rails");
var request = new SearchRepositoriesRequest("github");
request.Fork = ForkQualifier.IncludeForks;
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+fork:IncludeForks"));
}
[Fact]
@@ -410,10 +434,9 @@ namespace Octokit.Tests.Clients
//get repos whos language is Ruby
var request = new SearchRepositoriesRequest("github");
request.Language = Language.Ruby;
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+language:Ruby"));
}
[Fact]
@@ -425,8 +448,47 @@ namespace Octokit.Tests.Clients
var request = new SearchRepositoriesRequest("github");
request.In = new[] { InQualifier.Description };
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+in:Description"));
}
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
[Fact]
public void TestingTheInQualifier_Name()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
var request = new SearchRepositoriesRequest("github");
request.In = new[] { InQualifier.Name };
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+in:Name"));
}
[Fact]
public void TestingTheInQualifier_Readme()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
var request = new SearchRepositoriesRequest("github");
request.In = new[] { InQualifier.Readme };
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+in:Readme"));
}
[Fact]
public void TestingTheInQualifier_Multiple()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
var request = new SearchRepositoriesRequest("github");
request.In = new[] { InQualifier.Readme, InQualifier.Description, InQualifier.Name };
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+in:Readme,Description,Name"));
}
[Fact]
@@ -437,12 +499,52 @@ namespace Octokit.Tests.Clients
//get repos where the search contains 'github' and has been created after year jan 1 2011
var request = new SearchRepositoriesRequest("github");
request.Created = DateRange.GreaterThan(new DateTime(2011, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+created:>2011-01-01"));
}
[Fact]
public void TestingTheCreatedQualifier_GreaterThanOrEquals()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been created after year jan 1 2011
var request = new SearchRepositoriesRequest("github");
request.Created = DateRange.GreaterThanOrEquals(new DateTime(2011, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+created:>=2011-01-01"));
}
[Fact]
public void TestingTheCreatedQualifier_LessThan()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been created after year jan 1 2011
var request = new SearchRepositoriesRequest("github");
request.Created = DateRange.LessThan(new DateTime(2011, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+created:<2011-01-01"));
}
[Fact]
public void TestingTheCreatedQualifier_LessThanOrEquals()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been created after year jan 1 2011
var request = new SearchRepositoriesRequest("github");
request.Created = DateRange.LessThanOrEquals(new DateTime(2011, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+created:<=2011-01-01"));
}
[Fact]
public void TestingTheUpdatedQualifier()
{
@@ -450,11 +552,49 @@ namespace Octokit.Tests.Clients
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been pushed before year jan 1 2013
var request = new SearchRepositoriesRequest("github");
request.Updated = DateRange.LessThan(new DateTime(2013, 1, 1));
request.Updated = DateRange.GreaterThan(new DateTime(2013, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+pushed:>2013-01-01"));
}
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
[Fact]
public void TestingTheUpdatedQualifier_GreaterThanOrEquals()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been pushed before year jan 1 2013
var request = new SearchRepositoriesRequest("github");
request.Updated = DateRange.GreaterThanOrEquals(new DateTime(2013, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+pushed:>=2013-01-01"));
}
[Fact]
public void TestingTheUpdatedQualifier_LessThan()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been pushed before year jan 1 2013
var request = new SearchRepositoriesRequest("github");
request.Updated = DateRange.LessThan(new DateTime(2013, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+pushed:<2013-01-01"));
}
[Fact]
public void TestingTheUpdatedQualifier_LessThanOrEquals()
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the search contains 'github' and has been pushed before year jan 1 2013
var request = new SearchRepositoriesRequest("github");
request.Updated = DateRange.LessThanOrEquals(new DateTime(2013, 1, 1));
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+pushed:<=2013-01-01"));
}
[Fact]
@@ -462,13 +602,12 @@ namespace Octokit.Tests.Clients
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the Description contains rails and user/org is 'github'
var request = new SearchRepositoriesRequest("rails");
request.User = "github";
//get repos where search contains 'github' and user/org is 'github'
var request = new SearchRepositoriesRequest("github");
request.User = "rails";
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d => d["q"] == "github+user:rails"));
}
[Fact]
@@ -476,13 +615,16 @@ namespace Octokit.Tests.Clients
{
var connection = Substitute.For<IApiConnection>();
var client = new SearchClient(connection);
//get repos where the Description contains rails and user/org is 'github'
var request = new SearchRepositoriesRequest("rails");
request.Sort = RepoSearchSort.Forks;
var request = new SearchRepositoriesRequest("github");
request.SortField = RepoSearchSort.Stars;
client.SearchRepo(request);
connection.Received().Get<SearchRepositoryResult>(Arg.Is<Uri>(u => u.ToString() == "search/repositories"), Arg.Any<Dictionary<string, string>>());
connection.Received().Get<SearchRepositoryResult>(
Arg.Is<Uri>(u => u.ToString() == "search/repositories"),
Arg.Is<Dictionary<string, string>>(d =>
d["q"] == "github" &&
d["sort"] == "stars"));
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
public static class ReflectionExtensions
{
public static string GetAsyncVoidMethodsList(this Assembly assembly)
{
return String.Join("\r\n",
GetLoadableTypes(assembly)
.SelectMany(type => type.GetMethods())
.Where(HasAttribute<AsyncStateMachineAttribute>)
.Where(method => method.ReturnType == typeof(void))
.Select(method =>
String.Format("Method '{0}' of '{1}' has an async void return type and that's bad",
method.Name,
method.DeclaringType.Name))
.ToList());
}
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
public static bool HasAttribute<TAttribute>(this MethodInfo method) where TAttribute : Attribute
{
return method.GetCustomAttributes(typeof(TAttribute), false).Any();
}
}

View File

@@ -110,6 +110,7 @@
<Compile Include="Exceptions\RateLimitExceededExceptionTests.cs" />
<Compile Include="Exceptions\TwoFactorRequiredExceptionTests.cs" />
<Compile Include="Helpers\NSubstituteExtensions.cs" />
<Compile Include="Helpers\ReflectionExtensions.cs" />
<Compile Include="Helpers\UriExtensionsTests.cs" />
<Compile Include="Http\ApiConnectionTests.cs" />
<Compile Include="Clients\AuthorizationsClientTests.cs" />
@@ -170,6 +171,7 @@
<Compile Include="Reactive\ObservableTreesClientTests.cs" />
<Compile Include="Reactive\ObservableFollowersTest.cs" />
<Compile Include="Reactive\ObservableUserEmailsClientTests.cs" />
<Compile Include="SelfTests.cs" />
<Compile Include="SimpleJsonSerializerTests.cs" />
<Compile Include="Clients\UsersClientTests.cs" />
</ItemGroup>

View File

@@ -275,16 +275,31 @@ namespace Octokit.Tests.Reactive
public class TheCommitsMethod
{
[Fact]
public async void FetchesAllCommitsForPullRequest()
public async Task FetchesAllCommitsForPullRequest()
{
var commit = new PullRequestCommit();
var expectedUrl = string.Format("repos/fake/repo/pulls/42/commits");
var gitHubClient = Substitute.For<IGitHubClient>();
var connection = Substitute.For<IConnection>();
IResponse<List<PullRequestCommit>> response = new ApiResponse<List<PullRequestCommit>>
{
ApiInfo = new ApiInfo(
new Dictionary<string, Uri>(),
new List<string>(),
new List<string>(),
"",
new RateLimit(new Dictionary<string, string>())),
BodyAsObject = new List<PullRequestCommit> { commit }
};
connection.Get<List<PullRequestCommit>>(Args.Uri, null, null)
.Returns(Task.FromResult(response));
gitHubClient.Connection.Returns(connection);
var client = new ObservablePullRequestsClient(gitHubClient);
client.Commits("fake", "repo", 42);
var commits = await client.Commits("fake", "repo", 42).ToList();
Assert.Equal(1, commits.Count);
Assert.Same(commit, commits[0]);
connection.Received().Get<List<PullRequestCommit>>(new Uri(expectedUrl, UriKind.Relative), null, null);
}

View File

@@ -0,0 +1,14 @@
using Xunit;
/// <summary>
/// Tests to make sure our tests are ok.
/// </summary>
public class SelfTests
{
[Fact]
public void NoTestsUseAsyncVoid()
{
var errors = typeof(SelfTests).Assembly.GetAsyncVoidMethodsList();
Assert.Equal("", errors);
}
}

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
VisualStudioVersion = 12.0.30723.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit", "Octokit\Octokit.csproj", "{08DD4305-7787-4823-A53F-4D0F725A07F3}"
EndProject
@@ -11,6 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Octokit.Tests.Integration",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CEC9D451-6291-4EDF-971A-D398144FBF96}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
build.cmd = build.cmd
build.fsx = build.fsx
script\cibuild.ps1 = script\cibuild.ps1

View File

@@ -18,7 +18,7 @@ namespace Octokit
RateLimit rateLimit)
{
Ensure.ArgumentNotNull(links, "links");
Ensure.ArgumentNotNull(oauthScopes, "ouathScopes");
Ensure.ArgumentNotNull(oauthScopes, "oauthScopes");
Links = new ReadOnlyDictionary<string, Uri>(links);
OauthScopes = new ReadOnlyCollection<string>(oauthScopes);

View File

@@ -2,19 +2,22 @@
{
public class ProductHeaderValue
{
ProductHeaderValue() { }
readonly System.Net.Http.Headers.ProductHeaderValue _productHeaderValue;
public ProductHeaderValue(string name)
: this(new System.Net.Http.Headers.ProductHeaderValue(name))
{
_productHeaderValue = new System.Net.Http.Headers.ProductHeaderValue(name);
}
public ProductHeaderValue(string name, string value)
: this(new System.Net.Http.Headers.ProductHeaderValue(name, value))
{
_productHeaderValue = new System.Net.Http.Headers.ProductHeaderValue(name, value);
}
System.Net.Http.Headers.ProductHeaderValue _productHeaderValue;
ProductHeaderValue(System.Net.Http.Headers.ProductHeaderValue productHeader)
{
_productHeaderValue = productHeader;
}
public string Name
{
@@ -43,7 +46,7 @@
public static ProductHeaderValue Parse(string input)
{
return new ProductHeaderValue { _productHeaderValue = System.Net.Http.Headers.ProductHeaderValue.Parse(input) };
return new ProductHeaderValue(System.Net.Http.Headers.ProductHeaderValue.Parse(input));
}
public static bool TryParse(string input,

View File

@@ -11,7 +11,7 @@ namespace Octokit
[SuppressMessage("Microsoft.Design", "CA1012:AbstractTypesShouldNotHaveConstructors")]
public abstract class BaseSearchRequest
{
public BaseSearchRequest(string term)
protected BaseSearchRequest(string term)
{
Ensure.ArgumentNotNullOrEmptyString(term, "term");
Term = term;
@@ -80,15 +80,17 @@ namespace Octokit
{
get
{
var d = new Dictionary<string, string>();
d.Add("page", Page.ToString(CultureInfo.CurrentCulture));
d.Add("per_page", PerPage.ToString(CultureInfo.CurrentCulture));
var d = new Dictionary<string, string>
{
{ "page", Page.ToString(CultureInfo.CurrentCulture) }
, { "per_page", PerPage.ToString(CultureInfo.CurrentCulture) }
, { "order", SortOrder }
, { "q", TermAndQualifiers }
};
if (!String.IsNullOrWhiteSpace(Sort))
{
d.Add("sort", Sort);
}
d.Add("order", SortOrder);
d.Add("q", TermAndQualifiers);
return d;
}
}

View File

@@ -22,17 +22,17 @@ namespace Octokit
public string Description { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable downloads for the new repository. The default is true.
/// Optional. Gets or sets whether to enable downloads for the new repository. The default is true.
/// </summary>
public bool? HasDownloads { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable issues for the new repository. The default is true.
/// Optional. Gets or sets whether to enable issues for the new repository. The default is true.
/// </summary>
public bool? HasIssues { get; set; }
/// <summary>s
/// Optional. Gets or sets whether to the enable the wiki for the new repository. The default is true.
/// Optional. Gets or sets whether to enable the wiki for the new repository. The default is true.
/// </summary>
public bool? HasWiki { get; set; }

View File

@@ -13,43 +13,24 @@ namespace Octokit
/// http://developer.github.com/v3/search/#search-repositories
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class SearchRepositoriesRequest
public class SearchRepositoriesRequest : BaseSearchRequest
{
public SearchRepositoriesRequest(string term)
: base(term)
{
Term = term;
Page = 1;
PerPage = 100;
Fork = ForkQualifier.ExcludeForks;
Order = SortDirection.Descending;
}
/// <summary>
/// The search terms. This can be any combination of the supported repository search parameters:
/// http://developer.github.com/v3/search/#search-repositories
/// </summary>
public string Term { get; set; }
/// <summary>
/// For https://help.github.com/articles/searching-repositories#sorting
/// Optional Sort field. One of stars, forks, or updated. If not provided, results are sorted by best match.
/// </summary>
public RepoSearchSort? Sort { get; set; }
public RepoSearchSort? SortField { get; set; }
/// <summary>
/// Sort order one of asc or desc - the default is desc.
/// </summary>
public SortDirection Order { get; set; }
/// <summary>
/// Page of paginated results
/// </summary>
public int Page { get; set; }
/// <summary>
/// Number of items per page
/// </summary>
public int PerPage { get; set; }
public override string Sort
{
get { return SortField.ToParameter(); }
}
private IEnumerable<InQualifier> _inQualifier;
@@ -82,7 +63,7 @@ namespace Octokit
/// Defaults to ExcludeForks
/// https://help.github.com/articles/searching-repositories#forks
/// </summary>
public ForkQualifier Fork { get; set; }
public ForkQualifier? Fork { get; set; }
/// <summary>
/// The size qualifier finds repository's that match a certain size (in kilobytes).
@@ -120,7 +101,7 @@ namespace Octokit
/// </summary>
public DateRange Updated { get; set; }
public string MergeParameters()
public override IReadOnlyCollection<string> MergedQualifiers()
{
var parameters = new List<string>();
@@ -139,7 +120,10 @@ namespace Octokit
parameters.Add(String.Format(CultureInfo.InvariantCulture, "forks:{0}", Forks));
}
if (Fork != null)
{
parameters.Add(String.Format(CultureInfo.InvariantCulture, "fork:{0}", Fork));
}
if (Stars != null)
{
@@ -165,26 +149,7 @@ namespace Octokit
{
parameters.Add(String.Format(CultureInfo.InvariantCulture, "pushed:{0}", Updated));
}
return String.Join("+", parameters);
}
/// <summary>
/// get the params in the correct format...
/// </summary>
/// <returns></returns>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
public IDictionary<string, string> Parameters
{
get
{
var d = new System.Collections.Generic.Dictionary<string, string>();
d.Add("page", Page.ToString());
d.Add("per_page", PerPage.ToString());
d.Add("sort", Sort.ToString());
d.Add("q", Term + " " + MergeParameters()); //add qualifiers onto the search term
return d;
}
return parameters;
}
internal string DebuggerDisplay
@@ -222,7 +187,7 @@ namespace Octokit
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
public Range(int size)
{
query = size.ToString();
query = size.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
@@ -230,34 +195,30 @@ namespace Octokit
/// </summary>
/// <param name="minSize"></param>
/// <param name="maxSize"></param>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString")]
public Range(int minSize, int maxSize)
{
query = string.Format("{0}..{1}", minSize, maxSize);
query = string.Format(CultureInfo.InvariantCulture, "{0}..{1}", minSize, maxSize);
}
/// <summary>
/// Matches repositories with regards to the size <see cref="size"/>
/// We will use the <see cref="op"/> to see what operator will be applied to the size qualifier
/// </summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")]
public Range(int size, SearchQualifierOperator op)
{
switch (op)
{
case SearchQualifierOperator.GreaterThan:
query = string.Format(">{0}", size.ToString());
query = string.Format(CultureInfo.InvariantCulture, ">{0}", size);
break;
case SearchQualifierOperator.LessThan:
query = string.Format("<{0}", size.ToString());
query = string.Format(CultureInfo.InvariantCulture, "<{0}", size);
break;
case SearchQualifierOperator.LessThanOrEqualTo:
query = string.Format("<={0}", size.ToString());
query = string.Format(CultureInfo.InvariantCulture, "<={0}", size);
break;
case SearchQualifierOperator.GreaterThanOrEqualTo:
query = string.Format(">={0}", size.ToString());
break;
default:
query = string.Format(CultureInfo.InvariantCulture, ">={0}", size);
break;
}
}
@@ -306,7 +267,7 @@ namespace Octokit
/// </summary>
public class DateRange
{
private string query = string.Empty;
private readonly string query = string.Empty;
/// <summary>
/// Matches repositories with regards to the date <see cref="date"/>
@@ -314,24 +275,21 @@ namespace Octokit
/// </summary>
/// <param name="date">The date</param>
/// <param name="op">And its search operator</param>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.DateTime.ToString(System.String)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")]
public DateRange(DateTime date, SearchQualifierOperator op)
{
switch (op)
{
case SearchQualifierOperator.GreaterThan:
query = string.Format(">{0}", date.ToString("yyyy-MM-dd"));
query = string.Format(CultureInfo.InvariantCulture, ">{0:yyyy-MM-dd}", date);
break;
case SearchQualifierOperator.LessThan:
query = string.Format("<{0}", date.ToString("yyyy-MM-dd"));
query = string.Format(CultureInfo.InvariantCulture, "<{0:yyyy-MM-dd}", date);
break;
case SearchQualifierOperator.LessThanOrEqualTo:
query = string.Format("<={0}", date.ToString("yyyy-MM-dd"));
query = string.Format(CultureInfo.InvariantCulture, "<={0:yyyy-MM-dd}", date);
break;
case SearchQualifierOperator.GreaterThanOrEqualTo:
query = string.Format(">={0}", date.ToString("yyyy-MM-dd"));
break;
default:
query = string.Format(CultureInfo.InvariantCulture, ">={0:yyyy-MM-dd}", date);
break;
}
}
@@ -754,14 +712,17 @@ namespace Octokit
/// <summary>
/// search by number of stars
/// </summary>
[Parameter(Value = "stars")]
Stars,
/// <summary>
/// search by number of forks
/// </summary>
[Parameter(Value = "forks")]
Forks,
/// <summary>
/// search by last updated
/// </summary>
[Parameter(Value = "updated")]
Updated
}
@@ -774,14 +735,12 @@ namespace Octokit
/// <summary>
/// only search for forked repos
/// </summary>
[Parameter(Value = "Only")]
OnlyForks,
/// <summary>
/// include forked repos into the search
/// </summary>
IncludeForks,
/// <summary>
/// forks are not included in the search (default behaviour)
/// </summary>
ExcludeForks
[Parameter(Value = "True")]
IncludeForks
}
}

View File

@@ -15,5 +15,6 @@ namespace Octokit
public Author Committer { get; set; }
public string HtmlUrl { get; set; }
public IReadOnlyList<GitReference> Parents { get; set; }
public IReadOnlyList<GitHubCommitFile> Files { get; set; }
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Octokit
{
/// <summary>
/// The affected files in a <see cref="GitHubCommit"/>.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class GitHubCommitFile
{
/// <summary>
/// The name of the file
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")]
public string Filename { get; set; }
/// <summary>
/// Number of additions performed on the file.
/// </summary>
public int Additions { get; set; }
/// <summary>
/// Number of deletions performed on the file.
/// </summary>
public int Deletions { get; set; }
/// <summary>
/// Number of changes performed on the file.
/// </summary>
public int Changes { get; set; }
/// <summary>
/// File status, like modified, added, deleted.
/// </summary>
public string Status { get; set; }
/// <summary>
/// The url to the file blob.
/// </summary>
public string BlobUrl { get; set; }
/// <summary>
/// The url to file contents API.
/// </summary>
public string ContentsUrl { get; set; }
/// <summary>
/// The raw url to download the file.
/// </summary>
public string RawUrl { get; set; }
/// <summary>
/// The SHA of the file.
/// </summary>
public string Sha { get; set; }
internal string DebuggerDisplay
{
get
{
return String.Format(CultureInfo.InvariantCulture, "Filename: {0} ({1})", Filename, Status);
}
}
}
}

View File

@@ -130,6 +130,7 @@
<Compile Include="Models\Response\GistFile.cs" />
<Compile Include="Models\Response\GistFork.cs" />
<Compile Include="Models\Response\GistHistory.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\GitReference.cs" />
<Compile Include="Models\Response\GitTag.cs" />
<Compile Include="Models\Response\Issue.cs" />

View File

@@ -102,6 +102,7 @@
<Compile Include="Models\Response\Blob.cs" />
<Compile Include="Models\Response\CommitStatus.cs" />
<Compile Include="Models\Response\EventInfo.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\GitReference.cs" />
<Compile Include="Models\Response\GitTag.cs" />
<Compile Include="Models\Response\Issue.cs" />

View File

@@ -99,6 +99,7 @@
<Compile Include="Models\Response\Blob.cs" />
<Compile Include="Models\Response\CommitStatus.cs" />
<Compile Include="Models\Response\EventInfo.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\GitReference.cs" />
<Compile Include="Models\Response\GitTag.cs" />
<Compile Include="Models\Response\Issue.cs" />
@@ -350,5 +351,5 @@
<Compile Include="Models\Request\MarkAsReadRequest.cs" />
<Compile Include="Models\Request\NewThreadSubscription.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" />
</Project>

View File

@@ -222,6 +222,7 @@
<Compile Include="Models\Response\GistFile.cs" />
<Compile Include="Models\Response\GistFork.cs" />
<Compile Include="Models\Response\GistHistory.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\GitReference.cs" />
<Compile Include="Models\Response\GitTag.cs" />
<Compile Include="Models\Response\EventInfo.cs" />

View File

@@ -229,6 +229,7 @@
<Compile Include="Models\Response\GistFile.cs" />
<Compile Include="Models\Response\GistFork.cs" />
<Compile Include="Models\Response\GistHistory.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\GitReference.cs" />
<Compile Include="Models\Response\GitTag.cs" />
<Compile Include="Models\Response\EventInfo.cs" />

View File

@@ -81,6 +81,7 @@
<Compile Include="Models\Request\NotificationsRequest.cs" />
<Compile Include="Models\Request\PublicKey.cs" />
<Compile Include="Models\Request\CommitRequest.cs" />
<Compile Include="Models\Response\GitHubCommitFile.cs" />
<Compile Include="Models\Response\RepositoryPermissions.cs" />
<Compile Include="Models\Request\NewThreadSubscription.cs" />
<Compile Include="Models\Response\DeployKey.cs" />

13
appveyor.yml Normal file
View File

@@ -0,0 +1,13 @@
init:
- git config --global core.autocrlf input
build_script:
- cmd: build.cmd BuildApp
- cmd: build.cmd UnitTests
- cmd: build.cmd IntegrationTests -ev OCTOKIT_GITHUBUSERNAME "%OCTOKIT_GITHUBUSERNAME%" -ev OCTOKIT_GITHUBPASSWORD "%OCTOKIT_GITHUBPASSWORD%"
- cmd: build.cmd SourceLink
#- cmd: build.cmd CreatePackages
- cmd: build.cmd CreateOctokitPackage
- cmd: build.cmd CreateOctokitReactivePackage
test: off
artifacts:
- path: packaging\*.nupkg

View File

@@ -1,6 +1,7 @@
@echo off
"tools\nuget\nuget.exe" "install" "FAKE.Core" "-OutputDirectory" "tools" "-ExcludeVersion" "-version" "3.5.8"
"tools\nuget\nuget.exe" "install" "SourceLink.Fake" "-OutputDirectory" "tools" "-ExcludeVersion" "-version" "0.4.2"
:Build
cls

View File

@@ -1,6 +1,8 @@
#r @"tools\FAKE.Core\tools\FakeLib.dll"
#load "tools/SourceLink.Fake/tools/SourceLink.fsx"
open Fake
open System
open SourceLink
let authors = ["GitHub"]
@@ -27,6 +29,8 @@ let releaseNotes =
let buildMode = getBuildParamOrDefault "buildMode" "Release"
MSBuildDefaults <- { MSBuildDefaults with Verbosity = Some MSBuildVerbosity.Minimal }
Target "Clean" (fun _ ->
CleanDirs [buildDir; reactiveBuildDir; testResultsDir; packagingRoot; packagingDir; reactivePackagingDir]
)
@@ -94,6 +98,23 @@ Target "IntegrationTests" (fun _ ->
|> traceImportant
)
Target "SourceLink" (fun _ ->
use repo = new GitRepo(__SOURCE_DIRECTORY__)
[ "Octokit/Octokit.csproj"
"Octokit/Octokit-netcore45.csproj"
"Octokit/Octokit-Portable.csproj"
"Octokit.Reactive/Octokit.Reactive.csproj" ]
|> Seq.iter (fun pf ->
let proj = VsProj.LoadRelease pf
logfn "source linking %s" proj.OutputFilePdb
let files = (proj.Compiles -- "SolutionInfo.cs").SetBaseDirectory __SOURCE_DIRECTORY__
repo.VerifyChecksums files
proj.VerifyPdbChecksums files
proj.CreateSrcSrv "https://raw.githubusercontent.com/octokit/octokit.net/{0}/%var2%" repo.Revision (repo.Paths files)
Pdbstr.exec proj.OutputFilePdb proj.OutputFilePdbSrcSrv
)
)
Target "CreateOctokitPackage" (fun _ ->
let net45Dir = packagingDir @@ "lib/net45/"
let netcore45Dir = packagingDir @@ "lib/netcore45/"
@@ -102,10 +123,13 @@ Target "CreateOctokitPackage" (fun _ ->
CopyFile net45Dir (buildDir @@ "Release/Net45/Octokit.dll")
CopyFile net45Dir (buildDir @@ "Release/Net45/Octokit.XML")
CopyFile net45Dir (buildDir @@ "Release/Net45/Octokit.pdb")
CopyFile netcore45Dir (buildDir @@ "Release/NetCore45/Octokit.dll")
CopyFile netcore45Dir (buildDir @@ "Release/NetCore45/Octokit.XML")
CopyFile netcore45Dir (buildDir @@ "Release/NetCore45/Octokit.pdb")
CopyFile portableDir (buildDir @@ "Release/Portable/Octokit.dll")
CopyFile portableDir (buildDir @@ "Release/Portable/Octokit.XML")
CopyFile portableDir (buildDir @@ "Release/Portable/Octokit.pdb")
CopyFiles packagingDir ["LICENSE.txt"; "README.md"; "ReleaseNotes.md"]
NuGet (fun p ->
@@ -128,6 +152,7 @@ Target "CreateOctokitReactivePackage" (fun _ ->
CopyFile net45Dir (reactiveBuildDir @@ "Release/Net45/Octokit.Reactive.dll")
CopyFile net45Dir (reactiveBuildDir @@ "Release/Net45/Octokit.Reactive.XML")
CopyFile net45Dir (reactiveBuildDir @@ "Release/Net45/Octokit.Reactive.pdb")
CopyFiles reactivePackagingDir ["LICENSE.txt"; "README.md"; "ReleaseNotes.md"]
NuGet (fun p ->

63
docs/getting-started.md Normal file
View File

@@ -0,0 +1,63 @@
## Getting Started
The easiest way to get started with Octokit is to use a plain `GitHubClient`:
```
var client = new GitHubClient(new ProductHeaderValue("my-cool-app"));
```
This will let you access unauthenticated GitHub APIs, but you will be subject to rate limiting (you can read more about this [here](https://developer.github.com/v3/#rate-limiting)).
But why do you need this `ProductHeaderValue` value?
The API will reject you if you don't provide a `User-Agent` header (more details [here](https://developer.github.com/v3/#user-agent-required)). This is also to identify applications that are accessing the API and enable GitHub to contact the application author if there are problems. So pick a name that stands out!
### Authenticated Access
If you want to access private repositories or perform actions on behalf of a user, you need to pass credentials to the client.
There are two options supported by the API - basic and OAuth authentication.
```
var basicAuth = new Credentials("username", "password"); // NOTE: not real credentials
client.Credentials = basicAuth;
```
```
var tokenAuth = new Credentials("token"); // NOTE: not real token
client.Credentials = tokenAuth;
```
It is **strongly recommended** to use the [OAuth Flow](https://github.com/octokit/octokit.net/blob/master/docs/oauth-flow.md) for interactions on behalf of a user, as this gives two significant benefits:
- the application owner never needs to store a user's password
- the token can be revoked by the user at a later date
When authenticated, you have 5000 requests per hour available. So this is the recommended approach for interacting with the API.
### Connecting to GitHub Enterprise
Octokit also supports connecting to GitHub Enterprise environments - just provide the URL to your GitHub Enterprise server when creating the client.
```
var ghe = new Uri("https://github.myenterprise.com/");
var client = new GitHubClient(new ProductHeaderValue("my-cool-app"), ghe);
```
### Get exploring
Once you've got that setup, the simplest thing to experiment with is fetching details about a specific user:
```
var user = await client.User.Get("shiftkey");
Console.WriteLine("{0} has {1} public repositories - go check out their profile at {1}",
user.Name,
user.PublicRepos,
user.Url);
```
If you've authenticated as a given user, you can query their details directly:
```
var user = await client.User.Current();
```

View File

@@ -2,12 +2,16 @@
This is a place for putting together some introductory documentation about how to use Octokit.net.
- [Getting Started](https://github.com/octokit/octokit.net/blob/master/docs/getting-started.md)
- [OAuth Flow](https://github.com/octokit/octokit.net/blob/master/docs/oauth-flow.md)
- [Releases API](https://github.com/octokit/octokit.net/blob/master/docs/releases.md)
- [Git Database API](https://github.com/octokit/octokit.net/blob/master/docs/releases.md)
Possible topics to cover:
- Authentication
- Working with Repositories
- Working with User data
- Working with Git data
- Searching Repositories
- ...
@@ -16,4 +20,5 @@ If you're not sure where to start, there's a suite of
which can help you to get familiar with how things currently work.
Love and Octocats,
The Octokit.net Team

100
docs/oauth-flow.md Normal file
View File

@@ -0,0 +1,100 @@
## OAuth Flow
If you have a service which needs to interact with the GitHub API on behalf of a user, you should use the OAuth flow. [Phil Haack](https://haacked.com) has written a great [blog post](http://haacked.com/archive/2014/04/24/octokit-oauth/) on using Octokit in ASP.NET, and even has a [demo project](https://github.com/Haacked/octokit-oauth-demo) you can try out, so this will just go over the specific APIs in more detail.
### Setup
First, you need to register your application on GitHub (or GitHub Enterprise). While logged in, go to your account settings and click the Applications tab. Then click "Register new application".
Or just navigate to [https://github.com/settings/applications/new](https://github.com/settings/applications/new) if you're lazy.
Fill in some details about your application:
![application registration](https://cloud.githubusercontent.com/assets/19977/2760125/62600c38-c9ae-11e3-911f-783d7a34aeaf.png)
Then click "Register application", and you'll get your client id and client secret.
![OAuth application registration details](https://cloud.githubusercontent.com/assets/19977/2760128/95587e40-c9ae-11e3-84f2-053d2574f1e8.png)
### Starting the OAuth Flow
We'll use these in a few places, so define these as private fields:
```
var clientId = "some-id-here";
var clientSecret = "some-id-here";
var client = new GitHubClient(new ProductHeaderValue("my-cool-app"));
```
To start the authentication flow, you need to craft a URL indicating your application needs to authenticate on behalf of the current user.
```
// NOTE: this is not required, but highly recommended!
// ask the ASP.NET Membership provider to generate a random value
// and store it in the current user's session
string csrf = Membership.GeneratePassword(24, 1);
Session["CSRF:State"] = csrf;
var request = new OauthLoginRequest(clientId)
{
Scopes = {"user", "notifications"},
State = csrf
};
// NOTE: user must be navigated to this URL
var oauthLoginUrl = client.Oauth.GetGitHubLoginUrl(request);
```
Scopes are keys which specify the permissions the application needs. If you don't specify a `Scopes` value, your application will only have read access to the user's public data (repository, user info, etc). There's lots of different scopes available for different interactions with user data, so have a look at the [documentation](https://developer.github.com/v3/oauth/#scopes) for more information.
### Generating the token
Once the user has been navigated to the URL above and clicked "Authorize Application", you will receive a callback at the default Callback URL for your application. If you require a more flexible URL, you can override this by specifying a different URL when creating the request.
```
var request = new OauthLoginRequest(clientId)
{
// other parameters
RedirectUri = new Url("https://mycoolsite.com/some/uri")
};
```
The callback will have two parameters, the code generated by the GitHub API and some additional state - this is specifically to prevent CSRF (Cross-Site Request Forgery) attacks and is highly recommended.
With this code you can then request an access token by providing your client secret. This doesn't require any user interaction, so it can be done in the background.
```
public async Task<ActionResult> Authorize(string code, string state)
{
if (String.IsNullOrEmpty(code))
return RedirectToAction("Index");
var expectedState = Session["CSRF:State"] as string;
if (state != expectedState) throw new InvalidOperationException("SECURITY FAIL!");
Session["CSRF:State"] = null;
var request = new OauthTokenRequest(clientId, clientSecret, code);
var token = await client.Oauth.CreateAccessToken(request);
Session["OAuthToken"] = token.AccessToken;
return RedirectToAction("Index");
}
```
And now you have an access token, you can set up your credentials to use this token:
```
// repositories which include public and private repositories.
public async Task<ActionResult> Index()
{
var accessToken = Session["OAuthToken"] as string;
if (accessToken != null)
{
client.Credentials = new Credentials(accessToken);
var repositories = await client.Repository.GetAllForCurrent();
}
/* TODO: all the rest of the webapp */
}
```