diff --git a/Octokit.Reactive/IObservableGitHubClient.cs b/Octokit.Reactive/IObservableGitHubClient.cs index e6374a0c5e..be317491fb 100644 --- a/Octokit.Reactive/IObservableGitHubClient.cs +++ b/Octokit.Reactive/IObservableGitHubClient.cs @@ -1,6 +1,6 @@ namespace Octokit.Reactive { - public interface IObservableGitHubClient + public interface IObservableGitHubClient : IApiInfoProvider { IConnection Connection { get; } diff --git a/Octokit.Reactive/ObservableGitHubClient.cs b/Octokit.Reactive/ObservableGitHubClient.cs index dc5c3d0326..4970eebc9c 100644 --- a/Octokit.Reactive/ObservableGitHubClient.cs +++ b/Octokit.Reactive/ObservableGitHubClient.cs @@ -68,5 +68,14 @@ public IConnection Connection public IObservableNotificationsClient Notification { get; private set; } public IObservableGitDatabaseClient GitDatabase { get; private set; } public IObservableSearchClient Search { get; private set; } + + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo GetLastApiInfo() + { + return _gitHubClient.Connection.GetLastApiInfo(); + } } } diff --git a/Octokit.Tests.Integration/Clients/GitHubClientTests.cs b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs new file mode 100644 index 0000000000..aa7c6fcebb --- /dev/null +++ b/Octokit.Tests.Integration/Clients/GitHubClientTests.cs @@ -0,0 +1,83 @@ +using Octokit; +using Octokit.Tests.Integration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +public class GitHubClientTests +{ + public class TheLastApiInfoProperty + { + [IntegrationTest] + public async Task CanRetrieveLastApiInfoWithEtag() + { + // To check for etag, I'm using a new repository + // As per suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532 + var github = Helper.GetAuthenticatedClient(); + var repoName = Helper.MakeNameWithTimestamp("public-repo"); + + var createdRepository = await github.Repository.Create(new NewRepository(repoName)); + + try + { + var result = github.GetLastApiInfo(); + + Assert.True(result.Links.Count == 0); + Assert.True(result.AcceptedOauthScopes.Count > -1); + Assert.True(result.OauthScopes.Count > -1); + Assert.False(String.IsNullOrEmpty(result.Etag)); + Assert.True(result.RateLimit.Limit > 0); + Assert.True(result.RateLimit.Remaining > -1); + Assert.NotNull(result.RateLimit.Reset); + } + finally + { + Helper.DeleteRepo(createdRepository); + } + } + + [IntegrationTest] + public async Task CanRetrieveLastApiInfoWithLinks() + { + // To check for links, I'm doing a list of all contributors to the octokit.net project + // Adapted from suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532 + var github = Helper.GetAuthenticatedClient(); + + await github.Repository.GetAllContributors("octokit", "octokit.net"); + + var result = github.GetLastApiInfo(); + + Assert.True(result.Links.Count > 0); + Assert.True(result.AcceptedOauthScopes.Count > -1); + Assert.True(result.OauthScopes.Count > -1); + Assert.False(String.IsNullOrEmpty(result.Etag)); + Assert.True(result.RateLimit.Limit > 0); + Assert.True(result.RateLimit.Remaining > -1); + Assert.NotNull(result.RateLimit.Reset); + } + + [PersonalAccessTokenTest] + public async Task CanRetrieveLastApiInfoAcceptedOauth() + { + // To check for OAuth & AcceptedOAuth I'm getting the octokit user + // Adapted from suggestion here -> https://github.com/octokit/octokit.net/pull/855#issuecomment-126966532 + var github = Helper.GetAuthenticatedClient(); + + await github.User.Get("octokit"); + + var result = github.GetLastApiInfo(); + + Assert.True(result.Links.Count == 0); + Assert.True(result.AcceptedOauthScopes.Count > 0); + Assert.True(result.OauthScopes.Count > 0); + Assert.False(String.IsNullOrEmpty(result.Etag)); + Assert.True(result.RateLimit.Limit > 0); + Assert.True(result.RateLimit.Remaining > -1); + Assert.NotNull(result.RateLimit.Reset); + } + + } +} diff --git a/Octokit.Tests.Integration/Helper.cs b/Octokit.Tests.Integration/Helper.cs index 6959652097..8e623528d8 100644 --- a/Octokit.Tests.Integration/Helper.cs +++ b/Octokit.Tests.Integration/Helper.cs @@ -12,7 +12,7 @@ public static class Helper var githubUsername = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBUSERNAME"); UserName = githubUsername; Organization = Environment.GetEnvironmentVariable("OCTOKIT_GITHUBORGANIZATION"); - + var githubToken = Environment.GetEnvironmentVariable("OCTOKIT_OAUTHTOKEN"); if (githubToken != null) @@ -52,6 +52,14 @@ static Helper() public static Credentials ApplicationCredentials { get { return _oauthApplicationCredentials.Value; } } + public static bool IsUsingToken + { + get + { + return !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("OCTOKIT_OAUTHTOKEN")); + } + } + public static bool IsPaidAccount { get diff --git a/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs b/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs new file mode 100644 index 0000000000..0fd107ec66 --- /dev/null +++ b/Octokit.Tests.Integration/Helpers/PersonalAccessTokenTestAttribute.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Octokit.Tests.Integration +{ + public class PersonalAccessTokenTestDiscoverer : IXunitTestCaseDiscoverer + { + readonly IMessageSink diagnosticMessageSink; + + public PersonalAccessTokenTestDiscoverer(IMessageSink diagnosticMessageSink) + { + this.diagnosticMessageSink = diagnosticMessageSink; + } + + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + return Helper.IsUsingToken + ? new[] { new XunitTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) } + : Enumerable.Empty(); + } + } + + [XunitTestCaseDiscoverer("Octokit.Tests.Integration.PersonalAccessTokenTestDiscoverer", "Octokit.Tests.Integration")] + public class PersonalAccessTokenTestAttribute : FactAttribute + { + } +} diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj index 87dd6b7a9d..f6154d2829 100644 --- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj +++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj @@ -75,6 +75,7 @@ + @@ -101,6 +102,7 @@ + diff --git a/Octokit.Tests/GitHubClientTests.cs b/Octokit.Tests/GitHubClientTests.cs index 1b0fc24293..d3f8cb797d 100644 --- a/Octokit.Tests/GitHubClientTests.cs +++ b/Octokit.Tests/GitHubClientTests.cs @@ -5,6 +5,7 @@ using Octokit.Internal; using Xunit; using Xunit.Extensions; +using System.Collections.Generic; namespace Octokit.Tests { @@ -104,5 +105,71 @@ public void IsRetrievedFromCredentialStore() Assert.Equal("bar", client.Credentials.Password); } } + + public class TheLastApiInfoProperty + { + [Fact] + public async Task ReturnsNullIfNew() + { + var connection = Substitute.For(); + connection.GetLastApiInfo().Returns((ApiInfo)null); + var client = new GitHubClient(connection); + + var result = client.GetLastApiInfo(); + + Assert.Null(result); + + var temp = connection.Received(1).GetLastApiInfo(); + } + + [Fact] + public async Task ReturnsObjectIfNotNew() + { + var apiInfo = new ApiInfo( + new Dictionary + { + { + "next", + new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5") + }, + { + "last", + new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5") + }, + { + "first", + new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5") + }, + { + "prev", + new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5") + } + }, + new List + { + "user", + }, + new List + { + "user", + "public_repo", + "repo", + "gist" + }, + "5634b0b187fd2e91e3126a75006cc4fa", + new RateLimit(100, 75, 1372700873) + ); + var connection = Substitute.For(); + connection.GetLastApiInfo().Returns(apiInfo); + var client = new GitHubClient(connection); + + var result = client.GetLastApiInfo(); + + Assert.NotNull(result); + + var temp = connection.Received(1).GetLastApiInfo(); + } + } + } } diff --git a/Octokit.Tests/Http/ApiInfoTests.cs b/Octokit.Tests/Http/ApiInfoTests.cs new file mode 100644 index 0000000000..5971c58f31 --- /dev/null +++ b/Octokit.Tests/Http/ApiInfoTests.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Octokit.Tests.Http +{ + public class ApiInfoTests + { + public class TheMethods + { + [Fact] + public void CanClone() + { + var original = new ApiInfo( + new Dictionary + { + { + "next", + new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5") + }, + { + "last", + new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5") + }, + { + "first", + new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5") + }, + { + "prev", + new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5") + } + }, + new List + { + "user", + }, + new List + { + "user", + "public_repo", + "repo", + "gist" + }, + "5634b0b187fd2e91e3126a75006cc4fa", + new RateLimit(100, 75, 1372700873) + ); + + var clone = original.Clone(); + + // Note the use of Assert.NotSame tests for value types - this should continue to test should the underlying + // model are changed to Object types + Assert.NotSame(original, clone); + + Assert.Equal(original.Etag, clone.Etag); + Assert.NotSame(original.Etag, clone.Etag); + + Assert.Equal(original.AcceptedOauthScopes.Count, clone.AcceptedOauthScopes.Count); + Assert.NotSame(original.AcceptedOauthScopes, clone.AcceptedOauthScopes); + for (int i = 0; i < original.AcceptedOauthScopes.Count; i++) + { + Assert.Equal(original.AcceptedOauthScopes[i], clone.AcceptedOauthScopes[i]); + Assert.NotSame(original.AcceptedOauthScopes[i], clone.AcceptedOauthScopes[i]); + } + + Assert.Equal(original.Links.Count, clone.Links.Count); + Assert.NotSame(original.Links, clone.Links); + for (int i = 0; i < original.Links.Count; i++) + { + Assert.Equal(original.Links.Keys.ToArray()[i], clone.Links.Keys.ToArray()[i]); + Assert.NotSame(original.Links.Keys.ToArray()[i], clone.Links.Keys.ToArray()[i]); + Assert.Equal(original.Links.Values.ToArray()[i].ToString(), clone.Links.Values.ToArray()[i].ToString()); + Assert.NotSame(original.Links.Values.ToArray()[i], clone.Links.Values.ToArray()[i]); + } + + Assert.Equal(original.OauthScopes.Count, clone.OauthScopes.Count); + Assert.NotSame(original.OauthScopes, clone.OauthScopes); + for (int i = 0; i < original.OauthScopes.Count; i++) + { + Assert.Equal(original.OauthScopes[i], clone.OauthScopes[i]); + Assert.NotSame(original.OauthScopes[i], clone.OauthScopes[i]); + } + + Assert.NotSame(original.RateLimit, clone.RateLimit); + Assert.Equal(original.RateLimit.Limit, clone.RateLimit.Limit); + Assert.NotSame(original.RateLimit.Limit, clone.RateLimit.Limit); + Assert.Equal(original.RateLimit.Remaining, clone.RateLimit.Remaining); + Assert.NotSame(original.RateLimit.Remaining, clone.RateLimit.Remaining); + Assert.Equal(original.RateLimit.ResetAsUtcEpochSeconds, clone.RateLimit.ResetAsUtcEpochSeconds); + Assert.NotSame(original.RateLimit.ResetAsUtcEpochSeconds, clone.RateLimit.ResetAsUtcEpochSeconds); + Assert.Equal(original.RateLimit.Reset, clone.RateLimit.Reset); + Assert.NotSame(original.RateLimit.Reset, clone.RateLimit.Reset); + } + } + + } +} diff --git a/Octokit.Tests/Http/ConnectionTests.cs b/Octokit.Tests/Http/ConnectionTests.cs index eebc34d821..05c4eae1ca 100644 --- a/Octokit.Tests/Http/ConnectionTests.cs +++ b/Octokit.Tests/Http/ConnectionTests.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using NSubstitute; +using NSubstitute.Core.Arguments; using Octokit.Internal; using Octokit.Tests.Helpers; using Xunit; @@ -615,5 +617,91 @@ public void CreatesConnectionWithBaseAddress() Assert.True(connection.UserAgent.StartsWith("OctokitTests (")); } } + + public class TheLastAPiInfoProperty + { + [Fact] + public async Task ReturnsNullIfNew() + { + var httpClient = Substitute.For(); + var connection = new Connection(new ProductHeaderValue("OctokitTests"), + _exampleUri, + Substitute.For(), + httpClient, + Substitute.For()); + + var result = connection.GetLastApiInfo(); + + Assert.Null(result); + } + + [Fact] + public async Task ReturnsObjectIfNotNew() + { + var apiInfo = new ApiInfo( + new Dictionary + { + { + "next", + new Uri("https://api.github.com/repos/rails/rails/issues?page=4&per_page=5") + }, + { + "last", + new Uri("https://api.github.com/repos/rails/rails/issues?page=131&per_page=5") + }, + { + "first", + new Uri("https://api.github.com/repos/rails/rails/issues?page=1&per_page=5") + }, + { + "prev", + new Uri("https://api.github.com/repos/rails/rails/issues?page=2&per_page=5") + } + }, + new List + { + "user", + }, + new List + { + "user", + "public_repo", + "repo", + "gist" + }, + "5634b0b187fd2e91e3126a75006cc4fa", + new RateLimit(100, 75, 1372700873) + ); + + var httpClient = Substitute.For(); + + // We really only care about the ApiInfo property... + var expectedResponse = new Response(HttpStatusCode.OK, null, new Dictionary(), "application/json") + { + ApiInfo = apiInfo + }; + + httpClient.Send(Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(expectedResponse)); + + var connection = new Connection(new ProductHeaderValue("OctokitTests"), + _exampleUri, + Substitute.For(), + httpClient, + Substitute.For()); + + connection.Get(new Uri("https://example.com"), TimeSpan.MaxValue); + + var result = connection.GetLastApiInfo(); + + // No point checking all of the values as they are tested elsewhere + // Just provde that the ApiInfo is populated + Assert.Equal(4, result.Links.Count); + Assert.Equal(1, result.OauthScopes.Count); + Assert.Equal(4, result.AcceptedOauthScopes.Count); + Assert.Equal("5634b0b187fd2e91e3126a75006cc4fa", result.Etag); + Assert.Equal(100, result.RateLimit.Limit); + } + } } } diff --git a/Octokit.Tests/Http/RateLimitTests.cs b/Octokit.Tests/Http/RateLimitTests.cs index 3b0db94424..9473d05e63 100644 --- a/Octokit.Tests/Http/RateLimitTests.cs +++ b/Octokit.Tests/Http/RateLimitTests.cs @@ -109,6 +109,30 @@ public void EnsuresHeadersNotNull() { Assert.Throws(() => new RateLimit(null)); } + + } + + public class TheMethods + { + [Fact] + public void CanClone() + { + var original = new RateLimit(100, 42, 1372700873); + + var clone = original.Clone(); + + // Note the use of Assert.NotSame tests for value types - this should continue to test should the underlying + // model are changed to Object types + Assert.NotSame(original, clone); + Assert.Equal(original.Limit, clone.Limit); + Assert.NotSame(original.Limit, clone.Limit); + Assert.Equal(original.Remaining, clone.Remaining); + Assert.NotSame(original.Remaining, clone.Remaining); + Assert.Equal(original.ResetAsUtcEpochSeconds, clone.ResetAsUtcEpochSeconds); + Assert.NotSame(original.ResetAsUtcEpochSeconds, clone.ResetAsUtcEpochSeconds); + Assert.Equal(original.Reset, clone.Reset); + Assert.NotSame(original.Reset, clone.Reset); + } } } } \ No newline at end of file diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj index c9ae7fde8c..3c5335c573 100644 --- a/Octokit.Tests/Octokit.Tests.csproj +++ b/Octokit.Tests/Octokit.Tests.csproj @@ -130,6 +130,7 @@ + Code diff --git a/Octokit/GitHubClient.cs b/Octokit/GitHubClient.cs index d88a625720..6115f282a0 100644 --- a/Octokit/GitHubClient.cs +++ b/Octokit/GitHubClient.cs @@ -100,6 +100,15 @@ public GitHubClient(IConnection connection) Deployment = new DeploymentsClient(apiConnection); } + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo GetLastApiInfo() + { + return Connection.GetLastApiInfo(); + } + /// /// Convenience property for getting and setting credentials. /// diff --git a/Octokit/Helpers/CollectionExtensions.cs b/Octokit/Helpers/CollectionExtensions.cs index 15cc101bef..3481918c1d 100644 --- a/Octokit/Helpers/CollectionExtensions.cs +++ b/Octokit/Helpers/CollectionExtensions.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Linq; +using System.Collections.Generic; namespace Octokit { @@ -11,5 +13,37 @@ public static TValue SafeGet(this IReadOnlyDictionary Clone(this IReadOnlyList input) + { + List output = null; + if (input == null) + return output; + + output = new List(); + + foreach (var item in input) + { + output.Add(new String(item.ToCharArray())); + } + + return output; + } + + public static IDictionary Clone(this IReadOnlyDictionary input) + { + Dictionary output = null; + if (input == null) + return output; + + output = new Dictionary(); + + foreach (var item in input) + { + output.Add(new String(item.Key.ToCharArray()), new Uri(item.Value.ToString())); + } + + return output; + } } } diff --git a/Octokit/Http/ApiInfo.cs b/Octokit/Http/ApiInfo.cs index 54550cb76e..9a47f70de8 100644 --- a/Octokit/Http/ApiInfo.cs +++ b/Octokit/Http/ApiInfo.cs @@ -51,5 +51,25 @@ public ApiInfo(IDictionary links, /// Information about the API rate limit /// public RateLimit RateLimit { get; private set; } + + /// + /// Allows you to clone ApiInfo + /// + /// A clone of + public ApiInfo Clone() + { + // Seem to have to do this to pass a whole bunch of tests (for example Octokit.Tests.Clients.EventsClientTests.DeserializesCommitCommentEventCorrectly) + // I believe this has something to do with the Mocking framework. + if (this.Links == null || this.OauthScopes == null || this.RateLimit == null || this.Etag == null) + return null; + + return new ApiInfo(this.Links.Clone(), + this.OauthScopes.Clone(), + this.AcceptedOauthScopes.Clone(), + new String(this.Etag.ToCharArray()), + this.RateLimit.Clone()); + } + + } } diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 846923d3d8..5b086ca348 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -136,6 +136,20 @@ public Connection( _jsonPipeline = new JsonHttpPipeline(); } + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + public ApiInfo GetLastApiInfo() + { + // We've choosen to not wrap the _lastApiInfo in a lock. Originally the code was returning a reference - so there was a danger of + // on thread writing to the object while another was reading. Now we are cloning the ApiInfo on request - thus removing the need (or overhead) + // of putting locks in place. + // See https://github.com/octokit/octokit.net/pull/855#discussion_r36774884 + return _lastApiInfo == null ? null : _lastApiInfo.Clone(); + } + private ApiInfo _lastApiInfo; + public Task> Get(Uri uri, IDictionary parameters, string accepts) { Ensure.ArgumentNotNull(uri, "uri"); @@ -524,6 +538,11 @@ async Task RunRequest(IRequest request, CancellationToken cancellatio request.Headers.Add("User-Agent", UserAgent); await _authenticator.Apply(request).ConfigureAwait(false); var response = await _httpClient.Send(request, cancellationToken).ConfigureAwait(false); + if (response != null) + { + // Use the clone method to avoid keeping hold of the original (just in case it effect the lifetime of the whole response + _lastApiInfo = response.ApiInfo.Clone(); + } HandleErrors(response); return response; } diff --git a/Octokit/Http/IApiInfoProvider.cs b/Octokit/Http/IApiInfoProvider.cs new file mode 100644 index 0000000000..c0b5dfb987 --- /dev/null +++ b/Octokit/Http/IApiInfoProvider.cs @@ -0,0 +1,15 @@ +namespace Octokit +{ + /// + /// Provides a property for the Last recorded API infomation + /// + public interface IApiInfoProvider + { + /// + /// Gets the latest API Info - this will be null if no API calls have been made + /// + /// representing the information returned as part of an Api call + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + ApiInfo GetLastApiInfo(); + } +} diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 7deec1ccb8..bde7885afb 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -10,7 +10,7 @@ namespace Octokit /// /// A connection for making HTTP requests against URI endpoints. /// - public interface IConnection + public interface IConnection : IApiInfoProvider { /// /// Performs an asynchronous HTTP GET request that expects a containing HTML. diff --git a/Octokit/Http/RateLimit.cs b/Octokit/Http/RateLimit.cs index 6d30371164..6a3950eecc 100644 --- a/Octokit/Http/RateLimit.cs +++ b/Octokit/Http/RateLimit.cs @@ -99,5 +99,19 @@ internal string DebuggerDisplay } } + /// + /// Allows you to clone RateLimit + /// + /// A clone of + public RateLimit Clone() + { + return new RateLimit + { + Limit = this.Limit, + Remaining = this.Remaining, + ResetAsUtcEpochSeconds = this.ResetAsUtcEpochSeconds + }; + } + } } diff --git a/Octokit/Http/Response.cs b/Octokit/Http/Response.cs index 89b2750b3d..c143cdddc7 100644 --- a/Octokit/Http/Response.cs +++ b/Octokit/Http/Response.cs @@ -43,7 +43,7 @@ public Response(HttpStatusCode statusCode, object body, IDictionary /// Information about the API response parsed from the response headers. /// - public ApiInfo ApiInfo { get; private set; } + public ApiInfo ApiInfo { get; internal set; } // This setter is internal for use in tests. /// /// The response status code. /// diff --git a/Octokit/IGitHubClient.cs b/Octokit/IGitHubClient.cs index a828c83e42..7db4a5554c 100644 --- a/Octokit/IGitHubClient.cs +++ b/Octokit/IGitHubClient.cs @@ -5,7 +5,7 @@ namespace Octokit /// /// A Client for the GitHub API v3. You can read more about the api here: http://developer.github.com. /// - public interface IGitHubClient + public interface IGitHubClient : IApiInfoProvider { /// /// Provides a client connection to make rest requests to HTTP endpoints. diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj index 8b5eedaca9..77417e73f4 100644 --- a/Octokit/Octokit-Mono.csproj +++ b/Octokit/Octokit-Mono.csproj @@ -400,6 +400,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj index a34a7b1ef9..3ef8050e70 100644 --- a/Octokit/Octokit-MonoAndroid.csproj +++ b/Octokit/Octokit-MonoAndroid.csproj @@ -1,4 +1,4 @@ - + Debug @@ -416,6 +416,7 @@ + \ No newline at end of file diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj index 3f4b6f9cfa..51b7b67624 100644 --- a/Octokit/Octokit-Monotouch.csproj +++ b/Octokit/Octokit-Monotouch.csproj @@ -1,4 +1,4 @@ - + Debug @@ -409,6 +409,7 @@ + diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj index 36e840bd46..7e822d0785 100644 --- a/Octokit/Octokit-Portable.csproj +++ b/Octokit/Octokit-Portable.csproj @@ -399,6 +399,7 @@ + diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj index 17d8bae84e..862c55a477 100644 --- a/Octokit/Octokit-netcore45.csproj +++ b/Octokit/Octokit-netcore45.csproj @@ -403,6 +403,7 @@ + diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj index 760b5bfe67..b5c513c6fe 100644 --- a/Octokit/Octokit.csproj +++ b/Octokit/Octokit.csproj @@ -84,6 +84,7 @@ +