Merge remote-tracking branch 'upstream/master' into pr-495ext

Conflicts:
	Octokit/Octokit-MonoAndroid.csproj
	Octokit/Octokit-Monotouch.csproj
This commit is contained in:
Kristian Hald
2015-05-09 12:51:28 +02:00
54 changed files with 1489 additions and 199 deletions

View File

@@ -31,6 +31,56 @@ namespace Octokit.Reactive
Justification = "It's fiiiine. It's fine. Trust us.")] Justification = "It's fiiiine. It's fine. Trust us.")]
IObservable<Authorization> Get(int id); IObservable<Authorization> Get(int id);
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
IObservable<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization);
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="twoFactorAuthenticationCode">The two-factor authentication code in response to the current user's previous challenge</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
IObservable<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// This method will create a new authorization for the specified OAuth application, only if an authorization /// This method will create a new authorization for the specified OAuth application, only if an authorization
/// for that application doesnt already exist for the user. It returns the users token for the application /// for that application doesnt already exist for the user. It returns the users token for the application
@@ -133,10 +183,34 @@ namespace Octokit.Reactive
IObservable<Authorization> Update(int id, AuthorizationUpdate authorizationUpdate); IObservable<Authorization> Update(int id, AuthorizationUpdate authorizationUpdate);
/// <summary> /// <summary>
/// Deletes an <see cref="Authorization"/>. /// Deletes the specified <see cref="Authorization"/>.
/// </summary> /// </summary>
/// <param name="id">The systemwide id of the authorization</param> /// <remarks>
/// <returns></returns> /// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
IObservable<Unit> Delete(int id); IObservable<Unit> Delete(int id);
/// <summary>
/// Deletes the specified <see cref="Authorization"/>.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <param name="twoFactorAuthenticationCode">Two factor authorization code</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
IObservable<Unit> Delete(int id, string twoFactorAuthenticationCode);
} }
} }

View File

@@ -91,6 +91,24 @@ namespace Octokit.Reactive
/// <param name="since">Only gists updated at or after this time are returned</param> /// <param name="since">Only gists updated at or after this time are returned</param>
IObservable<Gist> GetAllForUser(string user, DateTimeOffset since); IObservable<Gist> GetAllForUser(string user, DateTimeOffset since);
/// <summary>
/// List gist commits
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-commits
/// </remarks>
/// <param name="id">The id of the gist</param>
IObservable<GistHistory> GetAllCommits(string id);
/// <summary>
/// List gist forks
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-forks
/// </remarks>
/// <param name="id">The id of the gist</param>
IObservable<GistFork> GetAllForks(string id);
/// <summary> /// <summary>
/// Creates a new gist /// Creates a new gist
/// </summary> /// </summary>

View File

@@ -24,6 +24,45 @@ namespace Octokit.Reactive
/// <returns></returns> /// <returns></returns>
IObservable<string> GetReadmeHtml(string owner, string name); IObservable<string> GetReadmeHtml(string owner, string name);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
IObservable<string> GetArchiveLink(string owner, string name);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <returns></returns>
IObservable<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <param name="reference">A valid Git reference.</param>
/// <returns></returns>
IObservable<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat, string reference);
/// <summary> /// <summary>
/// Returns the contents of a file or directory in a repository. /// Returns the contents of a file or directory in a repository.
/// </summary> /// </summary>

View File

@@ -45,6 +45,71 @@ namespace Octokit.Reactive
return _client.Get(id).ToObservable(); return _client.Get(id).ToObservable();
} }
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
public IObservable<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
Ensure.ArgumentNotNull(newAuthorization, "authorization");
return _client.Create(clientId, clientSecret, newAuthorization).ToObservable();
}
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="twoFactorAuthenticationCode">The two-factor authentication code in response to the current user's previous challenge</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
public IObservable<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
Ensure.ArgumentNotNull(newAuthorization, "authorization");
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
return _client.Create(clientId, clientSecret, newAuthorization, twoFactorAuthenticationCode).ToObservable();
}
/// <summary> /// <summary>
/// This method will create a new authorization for the specified OAuth application, only if an authorization /// This method will create a new authorization for the specified OAuth application, only if an authorization
/// for that application doesnt already exist for the user. It returns the users token for the application /// for that application doesnt already exist for the user. It returns the users token for the application
@@ -201,13 +266,40 @@ namespace Octokit.Reactive
} }
/// <summary> /// <summary>
/// Deletes an <see cref="Authorization"/>. /// Deletes the specified <see cref="Authorization"/>.
/// </summary> /// </summary>
/// <param name="id">The systemwide id of the authorization</param> /// <remarks>
/// <returns></returns> /// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
public IObservable<Unit> Delete(int id) public IObservable<Unit> Delete(int id)
{ {
return _client.Delete(id).ToObservable(); return _client.Delete(id).ToObservable();
} }
/// <summary>
/// Deletes the specified <see cref="Authorization"/>.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <param name="twoFactorAuthenticationCode">Two factor authorization code</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
public IObservable<Unit> Delete(int id, string twoFactorAuthenticationCode)
{
return _client.Delete(id, twoFactorAuthenticationCode).ToObservable();
}
} }
} }

View File

@@ -180,6 +180,34 @@ namespace Octokit.Reactive
return _connection.GetAndFlattenAllPages<Gist>(ApiUrls.UsersGists(user), request.ToParametersDictionary()); return _connection.GetAndFlattenAllPages<Gist>(ApiUrls.UsersGists(user), request.ToParametersDictionary());
} }
/// <summary>
/// List gist commits
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-commits
/// </remarks>
/// <param name="id">The id of the gist</param>
public IObservable<GistHistory> GetAllCommits(string id)
{
Ensure.ArgumentNotNullOrEmptyString(id, "id");
return _connection.GetAndFlattenAllPages<GistHistory>(ApiUrls.GistCommits(id));
}
/// <summary>
/// List gist forks
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-forks
/// </remarks>
/// <param name="id">The id of the gist</param>
public IObservable<GistFork> GetAllForks(string id)
{
Ensure.ArgumentNotNullOrEmptyString(id, "id");
return _connection.GetAndFlattenAllPages<GistFork>(ApiUrls.ForkGist(id));
}
/// <summary> /// <summary>
/// Edits a gist /// Edits a gist
/// </summary> /// </summary>

View File

@@ -116,7 +116,9 @@ namespace Octokit.Reactive
{ {
Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(request, "request");
return _connection.GetAndFlattenAllPages<Repository>(ApiUrls.AllPublicRepositories(), request.ToParametersDictionary()); var url = ApiUrls.AllPublicRepositories(request.Since);
return _connection.GetAndFlattenAllPages<Repository>(url);
} }
/// <summary> /// <summary>

View File

@@ -49,6 +49,57 @@ namespace Octokit.Reactive
} }
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public IObservable<string> GetArchiveLink(string owner, string name)
{
return GetArchiveLink(owner, name, ArchiveFormat.Tarball, string.Empty);
}
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <returns></returns>
public IObservable<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat)
{
return GetArchiveLink(owner, name, archiveFormat, String.Empty);
}
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <param name="reference">A valid Git reference.</param>
/// <returns></returns>
public IObservable<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat, string reference)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
return _client.Repository.Content.GetArchiveLink(owner, name, archiveFormat, reference).ToObservable();
}
/// <summary> /// <summary>
/// Returns the contents of a file or directory in a repository. /// Returns the contents of a file or directory in a repository.
/// </summary> /// </summary>

View File

@@ -33,8 +33,7 @@ namespace Octokit
string clientId, string clientId,
string clientSecret, string clientSecret,
NewAuthorization newAuthorization, NewAuthorization newAuthorization,
Func<TwoFactorRequiredException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHandler Func<TwoFactorRequiredException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHandler)
)
{ {
Ensure.ArgumentNotNull(authorizationsClient, "authorizationsClient"); Ensure.ArgumentNotNull(authorizationsClient, "authorizationsClient");
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId"); Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
@@ -55,5 +54,147 @@ namespace Octokit
newAuthorization, newAuthorization,
result.AuthenticationCode))); result.AuthenticationCode)));
} }
/// <summary>
/// This method will create a new authorization for the specified OAuth application. If an authorization
/// for that application already exists for the user and fingerprint, it'll delete the existing one and
/// recreate it.
/// </summary>
/// <remarks>
/// <para>
/// This method is typically used to initiate an application authentication flow.
/// This method allows the caller to provide a callback which is used to retrieve the two-factor code from
/// the user. Typically the callback is used to show some user interface to the user.
/// </para>
/// <para>
/// See <a href="http://developer.github.com/v3/oauth/#list-your-authorizations">API documentation</a>
/// for more details.
/// </para>
/// </remarks>
/// <param name="authorizationsClient">The <see cref="IAuthorizationsClient" /> this method extends</param>
/// <param name="clientId">Client ID for the OAuth application that is requesting the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="newAuthorization">Defines the scopes and metadata for the token</param>
/// <param name="twoFactorChallengeHandler">Callback used to retrieve the two-factor authentication code
/// from the user</param>
/// <param name="retryInvalidTwoFactorCode">If true, instead of completing when the two factor code supplied
/// is invalid, we go through the whole cycle again and prompt the two factor dialog.</param>
/// <returns></returns>
public static IObservable<ApplicationAuthorization> CreateAndDeleteExistingApplicationAuthorization(
this IObservableAuthorizationsClient authorizationsClient,
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHandler,
bool retryInvalidTwoFactorCode)
{
return authorizationsClient.CreateAndDeleteExistingApplicationAuthorization(
clientId,
clientSecret,
newAuthorization,
twoFactorChallengeHandler,
null,
retryInvalidTwoFactorCode);
}
public static IObservable<ApplicationAuthorization> CreateAndDeleteExistingApplicationAuthorization(
this IObservableAuthorizationsClient authorizationsClient,
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> twoFactorChallengeHandler,
string twoFactorAuthenticationCode,
bool retryInvalidTwoFactorCode)
{
Ensure.ArgumentNotNull(authorizationsClient, "authorizationsClient");
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
Ensure.ArgumentNotNull(newAuthorization, "newAuthorization");
// If retryInvalidTwoFactorCode is false, then we only show the TwoFactorDialog when we catch
// a TwoFactorRequiredException. If it's true, we show it for TwoFactorRequiredException and
// TwoFactorChallengeFailedException
Func<TwoFactorAuthorizationException, IObservable<TwoFactorChallengeResult>> twoFactorHandler = ex =>
retryInvalidTwoFactorCode || ex is TwoFactorRequiredException
? twoFactorChallengeHandler(ex)
: Observable.Throw<TwoFactorChallengeResult>(ex);
return authorizationsClient.CreateAuthorizationAndDeleteExisting(
clientId,
clientSecret,
newAuthorization,
twoFactorAuthenticationCode)
.Catch<ApplicationAuthorization, TwoFactorAuthorizationException>(
exception => twoFactorHandler(exception)
.SelectMany(result =>
result.ResendCodeRequested
? authorizationsClient.CreateAndDeleteExistingApplicationAuthorization(
clientId,
clientSecret,
newAuthorization,
twoFactorHandler,
null, // twoFactorAuthenticationCode
retryInvalidTwoFactorCode)
: authorizationsClient.CreateAndDeleteExistingApplicationAuthorization(
clientId,
clientSecret,
newAuthorization,
twoFactorHandler,
result.AuthenticationCode,
retryInvalidTwoFactorCode)));
}
// If the Application Authorization already exists, the result might have an empty string as the token. This is
// because GitHub.com no longer stores the token, but stores a hashed version of it. It is assumed that clients
// will store the token locally.
// The only reason to be calling GetOrCreateApplicationAuthentication is pretty much the case when you are
// logging in and thus don't have the token already. So if the token returned is an empty string, we'll go
// ahead and delete it for you and then recreate it.
static IObservable<ApplicationAuthorization> CreateAuthorizationAndDeleteExisting(
this IObservableAuthorizationsClient authorizationsClient,
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode = null)
{
return authorizationsClient.GetOrCreateAuthorizationUnified(
clientId,
clientSecret,
newAuthorization,
twoFactorAuthenticationCode)
.SelectMany(authorization => string.IsNullOrEmpty(authorization.Token)
? authorizationsClient.Delete(authorization.Id, twoFactorAuthenticationCode)
.SelectMany(_ =>
authorizationsClient.CreateNewAuthorization(
clientId,
clientSecret,
newAuthorization,
twoFactorAuthenticationCode))
: Observable.Return(authorization));
}
static IObservable<ApplicationAuthorization> GetOrCreateAuthorizationUnified(
this IObservableAuthorizationsClient authorizationsClient,
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode = null)
{
return string.IsNullOrEmpty(twoFactorAuthenticationCode)
? authorizationsClient.GetOrCreateApplicationAuthentication(clientId, clientSecret, newAuthorization)
: authorizationsClient.GetOrCreateApplicationAuthentication(clientId, clientSecret, newAuthorization, twoFactorAuthenticationCode);
}
static IObservable<ApplicationAuthorization> CreateNewAuthorization(
this IObservableAuthorizationsClient authorizationsClient,
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode = null)
{
return string.IsNullOrEmpty(twoFactorAuthenticationCode)
? authorizationsClient.Create(clientId, clientSecret, newAuthorization)
: authorizationsClient.Create(clientId, clientSecret, newAuthorization, twoFactorAuthenticationCode);
}
} }
} }

View File

@@ -22,10 +22,9 @@ namespace Octokit.Tests.Integration.Clients
Helper.ClientSecret, Helper.ClientSecret,
newAuthorization); newAuthorization);
Assert.NotNull(created);
Assert.False(String.IsNullOrWhiteSpace(created.Token)); Assert.False(String.IsNullOrWhiteSpace(created.Token));
Assert.True(String.IsNullOrWhiteSpace(created.TokenLastEight)); Assert.False(String.IsNullOrWhiteSpace(created.TokenLastEight));
Assert.True(String.IsNullOrWhiteSpace(created.HashedToken)); Assert.False(String.IsNullOrWhiteSpace(created.HashedToken));
// we can then query it through the regular API // we can then query it through the regular API
var get = await client.Authorization.Get(created.Id); var get = await client.Authorization.Get(created.Id);
@@ -42,13 +41,11 @@ namespace Octokit.Tests.Integration.Clients
Assert.Equal(created.Id, getExisting.Id); Assert.Equal(created.Id, getExisting.Id);
// NOTE: the old API will continue to return the full // the token is no longer returned for subsequent calls
// token if no Fingerprint is included Assert.True(String.IsNullOrWhiteSpace(getExisting.Token));
Assert.False(String.IsNullOrWhiteSpace(getExisting.Token)); // however the hashed and last 8 characters are available
Assert.False(String.IsNullOrWhiteSpace(getExisting.TokenLastEight));
// NOTE: and these new values are not included Assert.False(String.IsNullOrWhiteSpace(getExisting.HashedToken));
Assert.True(String.IsNullOrWhiteSpace(getExisting.TokenLastEight));
Assert.True(String.IsNullOrWhiteSpace(getExisting.HashedToken));
await client.Authorization.Delete(created.Id); await client.Authorization.Delete(created.Id);
} }

View File

@@ -34,7 +34,7 @@ namespace Octokit.Tests.Integration.Clients
Assert.True(_events.All(e => e.Payload != null)); Assert.True(_events.All(e => e.Payload != null));
} }
[IntegrationTest] [IntegrationTest(Skip = "no longer able to access this event")]
public void IssueCommentPayloadEventDeserializesCorrectly() public void IssueCommentPayloadEventDeserializesCorrectly()
{ {
var commentEvent = _events.FirstOrDefault(e => e.Id == "2628548686"); var commentEvent = _events.FirstOrDefault(e => e.Id == "2628548686");
@@ -49,7 +49,7 @@ namespace Octokit.Tests.Integration.Clients
Assert.Equal(742, commentPayload.Issue.Number); Assert.Equal(742, commentPayload.Issue.Number);
} }
[IntegrationTest] [IntegrationTest(Skip = "no longer able to access this event")]
public void PushEventPayloadDeserializesCorrectly() public void PushEventPayloadDeserializesCorrectly()
{ {
var pushEvent = _events.FirstOrDefault(e => e.Id == "2628858765"); var pushEvent = _events.FirstOrDefault(e => e.Id == "2628858765");
@@ -65,7 +65,7 @@ namespace Octokit.Tests.Integration.Clients
Assert.Equal(1, pushPayload.Size); Assert.Equal(1, pushPayload.Size);
} }
[IntegrationTest] [IntegrationTest(Skip = "no longer able to access this event")]
public void PREventPayloadDeserializesCorrectly() public void PREventPayloadDeserializesCorrectly()
{ {
var prEvent = _events.FirstOrDefault(e => e.Id == "2628718313"); var prEvent = _events.FirstOrDefault(e => e.Id == "2628718313");
@@ -79,7 +79,7 @@ namespace Octokit.Tests.Integration.Clients
Assert.Equal(743, prPayload.PullRequest.Number); Assert.Equal(743, prPayload.PullRequest.Number);
} }
[IntegrationTest] [IntegrationTest(Skip = "no longer able to access this event")]
public void PRReviewCommentEventPayloadDeserializesCorrectly() public void PRReviewCommentEventPayloadDeserializesCorrectly()
{ {
var prrcEvent = _events.First(e => e.Id == "2623246246"); var prrcEvent = _events.First(e => e.Id == "2623246246");

View File

@@ -117,4 +117,16 @@ public class GistsClientTests
await _fixture.Delete(createdGist.Id); await _fixture.Delete(createdGist.Id);
} }
[IntegrationTest]
public async Task CanGetGistChildren()
{
// Test History/Commits
var commits = await _fixture.GetAllCommits(testGistId);
Assert.NotEmpty(commits);
// Test Forks
var forks = await _fixture.GetAllForks(testGistId);
Assert.NotEmpty(forks);
}
} }

View File

@@ -553,7 +553,7 @@ public class RepositoriesClientTests
Assert.True(repositories.Count > 80); Assert.True(repositories.Count > 80);
} }
[IntegrationTest] [IntegrationTest(Skip = "Takes too long to run.")]
public async Task ReturnsAllPublicRepositoriesSinceLastSeen() public async Task ReturnsAllPublicRepositoriesSinceLastSeen()
{ {
var github = Helper.GetAuthenticatedClient(); var github = Helper.GetAuthenticatedClient();

View File

@@ -12,10 +12,7 @@ namespace Octokit.Tests.Integration.Clients
[IntegrationTest] [IntegrationTest]
public async Task ReturnsReadmeForSeeGit() public async Task ReturnsReadmeForSeeGit()
{ {
var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) var github = Helper.GetAuthenticatedClient();
{
Credentials = Helper.Credentials
};
var readme = await github.Repository.Content.GetReadme("octokit", "octokit.net"); var readme = await github.Repository.Content.GetReadme("octokit", "octokit.net");
Assert.Equal("README.md", readme.Name); Assert.Equal("README.md", readme.Name);
@@ -28,10 +25,7 @@ namespace Octokit.Tests.Integration.Clients
[IntegrationTest] [IntegrationTest]
public async Task ReturnsReadmeHtmlForSeeGit() public async Task ReturnsReadmeHtmlForSeeGit()
{ {
var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) var github = Helper.GetAuthenticatedClient();
{
Credentials = Helper.Credentials
};
var readmeHtml = await github.Repository.Content.GetReadmeHtml("octokit", "octokit.net"); var readmeHtml = await github.Repository.Content.GetReadmeHtml("octokit", "octokit.net");
Assert.True(readmeHtml.StartsWith("<div class=")); Assert.True(readmeHtml.StartsWith("<div class="));
@@ -45,10 +39,7 @@ namespace Octokit.Tests.Integration.Clients
[IntegrationTest] [IntegrationTest]
public async Task GetsFileContent() public async Task GetsFileContent()
{ {
var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) var github = Helper.GetAuthenticatedClient();
{
Credentials = Helper.Credentials
};
var contents = await github var contents = await github
.Repository .Repository
@@ -63,10 +54,7 @@ namespace Octokit.Tests.Integration.Clients
[IntegrationTest] [IntegrationTest]
public async Task GetsDirectoryContent() public async Task GetsDirectoryContent()
{ {
var github = new GitHubClient(new ProductHeaderValue("OctokitTests")) var github = Helper.GetAuthenticatedClient();
{
Credentials = Helper.Credentials
};
var contents = await github var contents = await github
.Repository .Repository
@@ -81,10 +69,8 @@ namespace Octokit.Tests.Integration.Clients
[IntegrationTest] [IntegrationTest]
public async Task CrudTest() public async Task CrudTest()
{ {
var client = new GitHubClient(new ProductHeaderValue("OctokitTests")) var client = Helper.GetAuthenticatedClient();
{
Credentials = Helper.Credentials
};
Repository repository = null; Repository repository = null;
try try
{ {
@@ -128,5 +114,44 @@ namespace Octokit.Tests.Integration.Clients
Helper.DeleteRepo(repository); Helper.DeleteRepo(repository);
} }
} }
[IntegrationTest]
public async Task GetsArchiveLinkAsTarball()
{
var github = Helper.GetAuthenticatedClient();
var archiveLink = await github
.Repository
.Content
.GetArchiveLink("octokit", "octokit.net");
Assert.Equal("https://codeload.github.com/octokit/octokit.net/legacy.tar.gz/master", archiveLink);
}
[IntegrationTest]
public async Task GetsArchiveLinkAsZipball()
{
var github = Helper.GetAuthenticatedClient();
var archiveLink = await github
.Repository
.Content
.GetArchiveLink("octokit", "octokit.net", ArchiveFormat.Zipball, "");
Assert.Equal("https://codeload.github.com/octokit/octokit.net/legacy.zip/master", archiveLink);
}
[IntegrationTest]
public async Task GetsArchiveLinkForReleaseBranchAsTarball()
{
var github = Helper.GetAuthenticatedClient();
var archiveLink = await github
.Repository
.Content
.GetArchiveLink("alfhenrik", "ScriptCs.OctoKit", ArchiveFormat.Tarball, "dev");
Assert.Equal("https://codeload.github.com/alfhenrik/ScriptCs.OctoKit/legacy.tar.gz/dev", archiveLink);
}
} }
} }

View File

@@ -32,5 +32,25 @@ public class HttpClientAdapterTests
Assert.Equal(78, imageBytes[2]); Assert.Equal(78, imageBytes[2]);
Assert.Equal(130, imageBytes.Last()); Assert.Equal(130, imageBytes.Last());
} }
[IntegrationTest]
public async Task CanCancelARequest()
{
var httpClient = new HttpClientAdapter();
var request = new Request
{
BaseAddress = new Uri("https://github.global.ssl.fastly.net/", UriKind.Absolute),
Endpoint = new Uri("/images/icons/emoji/poop.png?v=5", UriKind.RelativeOrAbsolute),
AllowAutoRedirect = true,
Method = HttpMethod.Get,
Timeout = TimeSpan.FromMilliseconds(10)
};
var response = httpClient.Send(request, CancellationToken.None);
await Task.Delay(TimeSpan.FromSeconds(2));
Assert.True(response.IsCanceled);
}
} }
} }

View File

@@ -36,8 +36,7 @@ namespace Octokit.Tests.Clients
client.Received().GetAll<Authorization>( client.Received().GetAll<Authorization>(
Arg.Is<Uri>(u => u.ToString() == "authorizations"), Arg.Is<Uri>(u => u.ToString() == "authorizations"),
null, null);
Arg.Any<string>());
} }
} }
@@ -53,8 +52,7 @@ namespace Octokit.Tests.Clients
client.Received().Get<Authorization>( client.Received().Get<Authorization>(
Arg.Is<Uri>(u => u.ToString() == "authorizations/1"), Arg.Is<Uri>(u => u.ToString() == "authorizations/1"),
null, null);
Arg.Any<string>());
} }
} }
@@ -238,9 +236,7 @@ namespace Octokit.Tests.Clients
authEndpoint.GetOrCreateApplicationAuthentication("clientId", "secret", data); authEndpoint.GetOrCreateApplicationAuthentication("clientId", "secret", data);
client.Received().Put<ApplicationAuthorization>(Arg.Is<Uri>(u => u.ToString() == "authorizations/clients/clientId/ha-ha-fingerprint"), client.Received().Put<ApplicationAuthorization>(Arg.Is<Uri>(u => u.ToString() == "authorizations/clients/clientId/ha-ha-fingerprint"),
Args.Object, Args.Object);
Args.String,
Args.String); // NOTE: preview API
} }
} }
@@ -256,8 +252,7 @@ namespace Octokit.Tests.Clients
client.Received().Get<ApplicationAuthorization>( client.Received().Get<ApplicationAuthorization>(
Arg.Is<Uri>(u => u.ToString() == "applications/clientId/tokens/accessToken"), Arg.Is<Uri>(u => u.ToString() == "applications/clientId/tokens/accessToken"),
null, null);
Arg.Any<string>());
} }
[Fact] [Fact]

View File

@@ -122,6 +122,44 @@ public class GistsClientTests
} }
} }
public class TheGetChildrenMethods
{
[Fact]
public async Task EnsureNonNullArguments()
{
var connection = Substitute.For<IApiConnection>();
var client = new GistsClient(connection);
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllCommits(null));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllCommits(""));
await Assert.ThrowsAsync<ArgumentNullException>(() => client.GetAllForks(null));
await Assert.ThrowsAsync<ArgumentException>(() => client.GetAllForks(""));
}
[Fact]
public void RequestsCorrectGetCommitsUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new GistsClient(connection);
client.GetAllCommits("9257657");
connection.Received().GetAll<GistHistory>(Arg.Is<Uri>(u => u.ToString() == "gists/9257657/commits"));
}
[Fact]
public void RequestsCorrectGetForksUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new GistsClient(connection);
client.GetAllForks("9257657");
connection.Received().GetAll<GistFork>(Arg.Is<Uri>(u => u.ToString() == "gists/9257657/forks"));
}
}
public class TheCreateMethod public class TheCreateMethod
{ {
[Fact] [Fact]

View File

@@ -285,8 +285,7 @@ namespace Octokit.Tests.Clients
client.GetAllPublic(new PublicRepositoryRequest(364)); client.GetAllPublic(new PublicRepositoryRequest(364));
connection.Received() connection.Received()
.GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "/repositories"), .GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "/repositories?since=364"));
Arg.Any<Dictionary<string, string>>());
} }
[Fact] [Fact]
@@ -298,9 +297,7 @@ namespace Octokit.Tests.Clients
client.GetAllPublic(new PublicRepositoryRequest(364)); client.GetAllPublic(new PublicRepositoryRequest(364));
connection.Received() connection.Received()
.GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "/repositories"), .GetAll<Repository>(Arg.Is<Uri>(u => u.ToString() == "/repositories?since=364"));
Arg.Is<Dictionary<string, string>>(d => d.Count == 1
&& d["since"] == "364"));
} }
} }

View File

@@ -2,6 +2,7 @@
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NSubstitute; using NSubstitute;
using Octokit.Tests.Helpers;
using Xunit; using Xunit;
namespace Octokit.Tests.Clients namespace Octokit.Tests.Clients
@@ -53,5 +54,59 @@ namespace Octokit.Tests.Clients
Assert.Equal("<html>README</html>", readme); Assert.Equal("<html>README</html>", readme);
} }
} }
public class TheGetArchiveLinkMethod
{
[Fact]
public async Task ReturnsArchiveLinkWithDefaults()
{
var connection = Substitute.For<IApiConnection>();
connection.GetRedirect(Args.Uri).Returns(Task.FromResult("https://codeload.github.com/fake/repo/legacy.tar.gz/master"));
var contentsClient = new RepositoryContentsClient(connection);
var archiveLink = await contentsClient.GetArchiveLink("fake", "repo");
connection.Received().GetRedirect(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/tarball/"));
Assert.Equal("https://codeload.github.com/fake/repo/legacy.tar.gz/master", archiveLink);
}
[Fact]
public async Task ReturnsArchiveLinkAsZipball()
{
var connection = Substitute.For<IApiConnection>();
connection.GetRedirect(Args.Uri).Returns(Task.FromResult("https://codeload.github.com/fake/repo/legacy.tar.gz/master"));
var contentsClient = new RepositoryContentsClient(connection);
var archiveLink = await contentsClient.GetArchiveLink("fake", "repo", ArchiveFormat.Zipball);
connection.Received().GetRedirect(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/zipball/"));
Assert.Equal("https://codeload.github.com/fake/repo/legacy.tar.gz/master", archiveLink);
}
[Fact]
public async Task ReturnsArchiveLinkWithSpecifiedValues()
{
var connection = Substitute.For<IApiConnection>();
connection.GetRedirect(Args.Uri).Returns(Task.FromResult("https://codeload.github.com/fake/repo/legazy.zip/release"));
var contentsClient = new RepositoryContentsClient(connection);
var archiveLink = await contentsClient.GetArchiveLink("fake", "repo", ArchiveFormat.Zipball, "release");
connection.Received().GetRedirect(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/zipball/release"));
Assert.Equal("https://codeload.github.com/fake/repo/legazy.zip/release", archiveLink);
}
[Fact]
public async Task EnsuresArgumentsNotNull()
{
var connection = Substitute.For<IApiConnection>();
var contentsClient = new RepositoryContentsClient(connection);
AssertEx.Throws<ArgumentNullException>(async () => await contentsClient.GetArchiveLink(null, "name"));
AssertEx.Throws<ArgumentNullException>(async () => await contentsClient.GetArchiveLink("owner", null));
AssertEx.Throws<ArgumentException>(async () => await contentsClient.GetArchiveLink("", "name"));
AssertEx.Throws<ArgumentException>(async () => await contentsClient.GetArchiveLink("owner", ""));
}
}
} }
} }

View File

@@ -16,11 +16,15 @@ namespace Octokit.Tests.Http
{ {
public class TheBuildRequestMessageMethod public class TheBuildRequestMessageMethod
{ {
readonly Uri _endpoint = new Uri("/ha-ha-business", UriKind.Relative);
[Fact] [Fact]
public void AddsHeadersToRequestMessage() public void AddsHeadersToRequestMessage()
{ {
var request = new Request var request = new Request
{ {
BaseAddress = GitHubClient.GitHubApiUrl,
Endpoint = _endpoint,
Method = HttpMethod.Post, Method = HttpMethod.Post,
Headers = Headers =
{ {
@@ -47,6 +51,8 @@ namespace Octokit.Tests.Http
{ {
var request = new Request var request = new Request
{ {
BaseAddress = GitHubClient.GitHubApiUrl,
Endpoint = _endpoint,
Method = HttpMethod.Post, Method = HttpMethod.Post,
Body = "{}", Body = "{}",
ContentType = "text/plain" ContentType = "text/plain"
@@ -64,6 +70,8 @@ namespace Octokit.Tests.Http
{ {
var request = new Request var request = new Request
{ {
BaseAddress = GitHubClient.GitHubApiUrl,
Endpoint = _endpoint,
Method = HttpMethod.Post, Method = HttpMethod.Post,
Body = new MemoryStream(), Body = new MemoryStream(),
ContentType = "text/plain" ContentType = "text/plain"
@@ -82,6 +90,8 @@ namespace Octokit.Tests.Http
{ {
var request = new Request var request = new Request
{ {
BaseAddress = GitHubClient.GitHubApiUrl,
Endpoint = _endpoint,
Method = HttpMethod.Post, Method = HttpMethod.Post,
Body = new FormUrlEncodedContent(new Dictionary<string, string> {{"foo", "bar"}}) Body = new FormUrlEncodedContent(new Dictionary<string, string> {{"foo", "bar"}})
}; };

View File

@@ -75,6 +75,7 @@
<Compile Include="Clients\IssuesClientTests.cs" /> <Compile Include="Clients\IssuesClientTests.cs" />
<Compile Include="Clients\IssuesEventsClientTests.cs" /> <Compile Include="Clients\IssuesEventsClientTests.cs" />
<Compile Include="Clients\IssuesLabelsClientTests.cs" /> <Compile Include="Clients\IssuesLabelsClientTests.cs" />
<Compile Include="Clients\MergingClientTests.cs" />
<Compile Include="Clients\MilestonesClientTests.cs" /> <Compile Include="Clients\MilestonesClientTests.cs" />
<Compile Include="Clients\MiscellaneousClientTests.cs" /> <Compile Include="Clients\MiscellaneousClientTests.cs" />
<Compile Include="Clients\NotificationsClientTests.cs" /> <Compile Include="Clients\NotificationsClientTests.cs" />
@@ -149,6 +150,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\StringExtensionsTests.cs" /> <Compile Include="Helpers\StringExtensionsTests.cs" />
<Compile Include="Clients\RepositoriesClientTests.cs" /> <Compile Include="Clients\RepositoriesClientTests.cs" />
<Compile Include="SelfTests.cs" />
<Compile Include="SimpleJsonSerializerTests.cs" /> <Compile Include="SimpleJsonSerializerTests.cs" />
<Compile Include="Clients\UsersClientTests.cs" /> <Compile Include="Clients\UsersClientTests.cs" />
<Compile Include="Clients\TeamsClientTests.cs" /> <Compile Include="Clients\TeamsClientTests.cs" />

View File

@@ -75,6 +75,7 @@
<Compile Include="Clients\IssuesClientTests.cs" /> <Compile Include="Clients\IssuesClientTests.cs" />
<Compile Include="Clients\IssuesEventsClientTests.cs" /> <Compile Include="Clients\IssuesEventsClientTests.cs" />
<Compile Include="Clients\IssuesLabelsClientTests.cs" /> <Compile Include="Clients\IssuesLabelsClientTests.cs" />
<Compile Include="Clients\MergingClientTests.cs" />
<Compile Include="Clients\MilestonesClientTests.cs" /> <Compile Include="Clients\MilestonesClientTests.cs" />
<Compile Include="Clients\MiscellaneousClientTests.cs" /> <Compile Include="Clients\MiscellaneousClientTests.cs" />
<Compile Include="Clients\NotificationsClientTests.cs" /> <Compile Include="Clients\NotificationsClientTests.cs" />
@@ -147,6 +148,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\StringExtensionsTests.cs" /> <Compile Include="Helpers\StringExtensionsTests.cs" />
<Compile Include="Clients\RepositoriesClientTests.cs" /> <Compile Include="Clients\RepositoriesClientTests.cs" />
<Compile Include="SelfTests.cs" />
<Compile Include="SimpleJsonSerializerTests.cs" /> <Compile Include="SimpleJsonSerializerTests.cs" />
<Compile Include="Clients\UsersClientTests.cs" /> <Compile Include="Clients\UsersClientTests.cs" />
<Compile Include="Clients\TeamsClientTests.cs" /> <Compile Include="Clients\TeamsClientTests.cs" />

View File

@@ -181,6 +181,7 @@
<Compile Include="Reactive\ObservablePullRequestReviewCommentsClientTests.cs" /> <Compile Include="Reactive\ObservablePullRequestReviewCommentsClientTests.cs" />
<Compile Include="Reactive\ObservableRepositoriesClientTests.cs" /> <Compile Include="Reactive\ObservableRepositoriesClientTests.cs" />
<Compile Include="Reactive\ObservableRepositoryDeployKeysClientTests.cs" /> <Compile Include="Reactive\ObservableRepositoryDeployKeysClientTests.cs" />
<Compile Include="Reactive\ObservableGistsTests.cs" />
<Compile Include="Reactive\ObservableStarredClientTests.cs" /> <Compile Include="Reactive\ObservableStarredClientTests.cs" />
<Compile Include="Reactive\ObservableStatisticsClientTests.cs" /> <Compile Include="Reactive\ObservableStatisticsClientTests.cs" />
<Compile Include="Reactive\ObservableTreesClientTests.cs" /> <Compile Include="Reactive\ObservableTreesClientTests.cs" />

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using NSubstitute;
using Octokit;
using Octokit.Internal;
using Octokit.Reactive;
using Octokit.Reactive.Internal;
using Octokit.Tests.Helpers;
using Xunit;
using Xunit.Extensions;
namespace Octokit.Tests.Reactive
{
public class ObservableGistsTests
{
public class TheGetChildrenMethods
{
[Fact]
public async Task EnsureNonNullArguments()
{
var client = new ObservableGistsClient(Substitute.For<IGitHubClient>());
await AssertEx.Throws<ArgumentNullException>(async () => await client.GetAllCommits(null));
await AssertEx.Throws<ArgumentException>(async () => await client.GetAllCommits(""));
await AssertEx.Throws<ArgumentNullException>(async () => await client.GetAllForks(null));
await AssertEx.Throws<ArgumentException>(async () => await client.GetAllForks(""));
}
[Fact]
public void RequestsCorrectGetCommitsUrl()
{
var github = Substitute.For<IGitHubClient>();
var client = new ObservableGistsClient(github);
var expected = new Uri("gists/9257657/commits", UriKind.Relative);
client.GetAllCommits("9257657");
github.Connection.Received(1).Get<List<GistHistory>>(expected, Arg.Any<IDictionary<string, string>>(), null);
}
[Fact]
public void RequestsCorrectGetForksUrl()
{
var github = Substitute.For<IGitHubClient>();
var client = new ObservableGistsClient(github);
var expected = new Uri("gists/9257657/forks", UriKind.Relative);
client.GetAllForks("9257657");
github.Connection.Received(1).Get<List<GistFork>>(expected, Arg.Any<IDictionary<string, string>>(), null);
}
}
}
}

View File

@@ -163,7 +163,7 @@ namespace Octokit.Tests.Reactive
[Fact] [Fact]
public async Task ReturnsEveryPageOfRepositories() public async Task ReturnsEveryPageOfRepositories()
{ {
var firstPageUrl = new Uri("/repositories", UriKind.Relative); var firstPageUrl = new Uri("/repositories?since=364", UriKind.Relative);
var secondPageUrl = new Uri("https://example.com/page/2"); var secondPageUrl = new Uri("https://example.com/page/2");
var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } }; var firstPageLinks = new Dictionary<string, Uri> { { "next", secondPageUrl } };
IApiResponse<List<Repository>> firstPageResponse = new ApiResponse<List<Repository>>( IApiResponse<List<Repository>> firstPageResponse = new ApiResponse<List<Repository>>(
@@ -195,9 +195,7 @@ namespace Octokit.Tests.Reactive
}); });
var gitHubClient = Substitute.For<IGitHubClient>(); var gitHubClient = Substitute.For<IGitHubClient>();
gitHubClient.Connection.Get<List<Repository>>(firstPageUrl, gitHubClient.Connection.Get<List<Repository>>(firstPageUrl, null, null)
Arg.Is<Dictionary<string, string>>(d => d.Count == 1
&& d["since"] == "364"), null)
.Returns(Task.FromResult(firstPageResponse)); .Returns(Task.FromResult(firstPageResponse));
gitHubClient.Connection.Get<List<Repository>>(secondPageUrl, null, null) gitHubClient.Connection.Get<List<Repository>>(secondPageUrl, null, null)
.Returns(Task.FromResult(secondPageResponse)); .Returns(Task.FromResult(secondPageResponse));
@@ -209,9 +207,7 @@ namespace Octokit.Tests.Reactive
var results = await repositoriesClient.GetAllPublic(new PublicRepositoryRequest(364)).ToArray(); var results = await repositoriesClient.GetAllPublic(new PublicRepositoryRequest(364)).ToArray();
Assert.Equal(7, results.Length); Assert.Equal(7, results.Length);
gitHubClient.Connection.Received(1).Get<List<Repository>>(firstPageUrl, gitHubClient.Connection.Received(1).Get<List<Repository>>(firstPageUrl, null, null);
Arg.Is<Dictionary<string, string>>(d=>d.Count == 1
&& d["since"] == "364"), null);
gitHubClient.Connection.Received(1).Get<List<Repository>>(secondPageUrl, null, null); gitHubClient.Connection.Received(1).Get<List<Repository>>(secondPageUrl, null, null);
gitHubClient.Connection.Received(1).Get<List<Repository>>(thirdPageUrl, null, null); gitHubClient.Connection.Received(1).Get<List<Repository>>(thirdPageUrl, null, null);
} }

View File

@@ -14,8 +14,6 @@ namespace Octokit
/// </remarks> /// </remarks>
public class AuthorizationsClient : ApiClient, IAuthorizationsClient public class AuthorizationsClient : ApiClient, IAuthorizationsClient
{ {
const string previewAcceptsHeader = "application/vnd.github.mirage-preview+json";
/// <summary> /// <summary>
/// Initializes a new GitHub OAuth API client. /// Initializes a new GitHub OAuth API client.
/// </summary> /// </summary>
@@ -38,7 +36,7 @@ namespace Octokit
/// <returns>A list of <see cref="Authorization"/>s.</returns> /// <returns>A list of <see cref="Authorization"/>s.</returns>
public Task<IReadOnlyList<Authorization>> GetAll() public Task<IReadOnlyList<Authorization>> GetAll()
{ {
return ApiConnection.GetAll<Authorization>(ApiUrls.Authorizations(), null, previewAcceptsHeader); return ApiConnection.GetAll<Authorization>(ApiUrls.Authorizations(), null);
} }
/// <summary> /// <summary>
@@ -56,7 +54,95 @@ namespace Octokit
/// <returns>The specified <see cref="Authorization"/>.</returns> /// <returns>The specified <see cref="Authorization"/>.</returns>
public Task<Authorization> Get(int id) public Task<Authorization> Get(int id)
{ {
return ApiConnection.Get<Authorization>(ApiUrls.Authorizations(id), null, previewAcceptsHeader); return ApiConnection.Get<Authorization>(ApiUrls.Authorizations(id), null);
}
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
public Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
Ensure.ArgumentNotNull(newAuthorization, "authorization");
var requestData = new
{
client_id = clientId,
client_secret = clientSecret,
scopes = newAuthorization.Scopes,
note = newAuthorization.Note,
note_url = newAuthorization.NoteUrl,
fingerprint = newAuthorization.Fingerprint
};
var endpoint = ApiUrls.Authorizations();
return ApiConnection.Post<ApplicationAuthorization>(endpoint, requestData);
}
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="twoFactorAuthenticationCode">The two-factor authentication code in response to the current user's previous challenge</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
public Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, "clientId");
Ensure.ArgumentNotNullOrEmptyString(clientSecret, "clientSecret");
Ensure.ArgumentNotNull(newAuthorization, "authorization");
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
var requestData = new
{
client_id = clientId,
client_secret = clientSecret,
scopes = newAuthorization.Scopes,
note = newAuthorization.Note,
note_url = newAuthorization.NoteUrl,
fingerprint = newAuthorization.Fingerprint
};
var endpoint = ApiUrls.Authorizations();
return ApiConnection.Post<ApplicationAuthorization>(endpoint, requestData, null, null, twoFactorAuthenticationCode);
} }
/// <summary> /// <summary>
@@ -95,18 +181,11 @@ namespace Octokit
note_url = newAuthorization.NoteUrl note_url = newAuthorization.NoteUrl
}; };
if (String.IsNullOrWhiteSpace(newAuthorization.Fingerprint)) var endpoint = string.IsNullOrWhiteSpace(newAuthorization.Fingerprint)
{ ? ApiUrls.AuthorizationsForClient(clientId)
// use classic API : ApiUrls.AuthorizationsForClient(clientId, newAuthorization.Fingerprint);
var endpoint = ApiUrls.AuthorizationsForClient(clientId);
return ApiConnection.Put<ApplicationAuthorization>(endpoint, requestData); return ApiConnection.Put<ApplicationAuthorization>(endpoint, requestData);
}
else
{
// use new API
var endpoint = ApiUrls.AuthorizationsForClient(clientId, newAuthorization.Fingerprint);
return ApiConnection.Put<ApplicationAuthorization>(endpoint, requestData, null, previewAcceptsHeader);
}
} }
/// <summary> /// <summary>
@@ -150,29 +229,18 @@ namespace Octokit
try try
{ {
if (String.IsNullOrWhiteSpace(newAuthorization.Fingerprint)) var endpoint = string.IsNullOrWhiteSpace(newAuthorization.Fingerprint)
{ ? ApiUrls.AuthorizationsForClient(clientId)
// use classic API : ApiUrls.AuthorizationsForClient(clientId, newAuthorization.Fingerprint);
var endpoint = ApiUrls.AuthorizationsForClient(clientId);
return await ApiConnection.Put<ApplicationAuthorization>( return await ApiConnection.Put<ApplicationAuthorization>(
endpoint, endpoint,
requestData, requestData,
twoFactorAuthenticationCode); twoFactorAuthenticationCode);
}
else
{
// use new API
var endpoint = ApiUrls.AuthorizationsForClient(clientId, newAuthorization.Fingerprint);
return await ApiConnection.Put<ApplicationAuthorization>(
endpoint,
requestData,
twoFactorAuthenticationCode,
previewAcceptsHeader);
}
} }
catch (AuthorizationException e) catch (AuthorizationException e)
{ {
throw new TwoFactorChallengeFailedException(e); throw new TwoFactorChallengeFailedException(twoFactorAuthenticationCode, e);
} }
} }
@@ -194,8 +262,7 @@ namespace Octokit
var endpoint = ApiUrls.ApplicationAuthorization(clientId, accessToken); var endpoint = ApiUrls.ApplicationAuthorization(clientId, accessToken);
return await ApiConnection.Get<ApplicationAuthorization>( return await ApiConnection.Get<ApplicationAuthorization>(
endpoint, endpoint,
null, null);
previewAcceptsHeader);
} }
/// <summary> /// <summary>
@@ -274,19 +341,9 @@ namespace Octokit
{ {
Ensure.ArgumentNotNull(authorizationUpdate, "authorizationUpdate"); Ensure.ArgumentNotNull(authorizationUpdate, "authorizationUpdate");
if (String.IsNullOrWhiteSpace(authorizationUpdate.Fingerprint)) return ApiConnection.Patch<Authorization>(
{ ApiUrls.Authorizations(id),
return ApiConnection.Patch<Authorization>( authorizationUpdate);
ApiUrls.Authorizations(id),
authorizationUpdate);
}
else
{
return ApiConnection.Patch<Authorization>(
ApiUrls.Authorizations(id),
authorizationUpdate,
previewAcceptsHeader);
}
} }
/// <summary> /// <summary>
@@ -307,5 +364,25 @@ namespace Octokit
{ {
return ApiConnection.Delete(ApiUrls.Authorizations(id)); return ApiConnection.Delete(ApiUrls.Authorizations(id));
} }
/// <summary>
/// Deletes the specified <see cref="Authorization"/>.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <param name="twoFactorAuthenticationCode">Two factor authorization code</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
public Task Delete(int id, string twoFactorAuthenticationCode)
{
return ApiConnection.Delete(ApiUrls.Authorizations(id), twoFactorAuthenticationCode);
}
} }
} }

View File

@@ -196,6 +196,34 @@ namespace Octokit
return ApiConnection.GetAll<Gist>(ApiUrls.UsersGists(user), request.ToParametersDictionary()); return ApiConnection.GetAll<Gist>(ApiUrls.UsersGists(user), request.ToParametersDictionary());
} }
/// <summary>
/// List gist commits
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-commits
/// </remarks>
/// <param name="id">The id of the gist</param>
public Task<IReadOnlyList<GistHistory>> GetAllCommits(string id)
{
Ensure.ArgumentNotNullOrEmptyString(id, "id");
return ApiConnection.GetAll<GistHistory>(ApiUrls.GistCommits(id));
}
/// <summary>
/// List gist forks
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-forks
/// </remarks>
/// <param name="id">The id of the gist</param>
public Task<IReadOnlyList<GistFork>> GetAllForks(string id)
{
Ensure.ArgumentNotNullOrEmptyString(id, "id");
return ApiConnection.GetAll<GistFork>(ApiUrls.ForkGist(id));
}
/// <summary> /// <summary>
/// Edits a gist /// Edits a gist
/// </summary> /// </summary>

View File

@@ -49,6 +49,56 @@ namespace Octokit
Justification = "It's fiiiine. It's fine. Trust us.")] Justification = "It's fiiiine. It's fine. Trust us.")]
Task<Authorization> Get(int id); Task<Authorization> Get(int id);
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization);
/// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application
/// doesnt already exist for the user; otherwise, it fails.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#get-or-create-an-authorization-for-a-specific-app">API documentation</a> for more information.
/// </remarks>
/// <param name="clientId">Client ID of the OAuth application for the token</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="twoFactorAuthenticationCode">The two-factor authentication code in response to the current user's previous challenge</param>
/// <param name="newAuthorization">Describes the new authorization to create</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make this request.
/// </exception>
/// <exception cref="TwoFactorRequiredException">
/// Thrown when the current account has two-factor authentication enabled and an authentication code is required.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>The created <see cref="Authorization"/>.</returns>
Task<ApplicationAuthorization> Create(
string clientId,
string clientSecret,
NewAuthorization newAuthorization,
string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// Creates a new authorization for the specified OAuth application if an authorization for that application doesnt already /// Creates a new authorization for the specified OAuth application if an authorization for that application doesnt already
/// exist for the user; otherwise, returns the users existing authorization for that application. /// exist for the user; otherwise, returns the users existing authorization for that application.
@@ -178,5 +228,22 @@ namespace Octokit
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception> /// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A <see cref="Task"/> for the request's execution.</returns> /// <returns>A <see cref="Task"/> for the request's execution.</returns>
Task Delete(int id); Task Delete(int id);
/// <summary>
/// Deletes the specified <see cref="Authorization"/>.
/// </summary>
/// <remarks>
/// This method requires authentication.
/// See the <a href="http://developer.github.com/v3/oauth/#delete-an-authorization">API
/// documentation</a> for more details.
/// </remarks>
/// <param name="id">The system-wide ID of the authorization to delete</param>
/// <param name="twoFactorAuthenticationCode">Two factor authorization code</param>
/// <exception cref="AuthorizationException">
/// Thrown when the current user does not have permission to make the request.
/// </exception>
/// <exception cref="ApiException">Thrown when a general API error occurs.</exception>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
Task Delete(int id, string twoFactorAuthenticationCode);
} }
} }

View File

@@ -98,6 +98,24 @@ namespace Octokit
/// <param name="since">Only gists updated at or after this time are returned</param> /// <param name="since">Only gists updated at or after this time are returned</param>
Task<IReadOnlyList<Gist>> GetAllForUser(string user, DateTimeOffset since); Task<IReadOnlyList<Gist>> GetAllForUser(string user, DateTimeOffset since);
/// <summary>
/// List gist commits
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-commits
/// </remarks>
/// <param name="id">The id of the gist</param>
Task<IReadOnlyList<GistHistory>> GetAllCommits(string id);
/// <summary>
/// List gist forks
/// </summary>
/// <remarks>
/// http://developer.github.com/v3/gists/#list-gists-forks
/// </remarks>
/// <param name="id">The id of the gist</param>
Task<IReadOnlyList<GistFork>> GetAllForks(string id);
/// <summary> /// <summary>
/// Creates a new gist /// Creates a new gist
/// </summary> /// </summary>

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Octokit.Internal;
namespace Octokit namespace Octokit
{ {
@@ -46,6 +47,45 @@ namespace Octokit
/// <returns></returns> /// <returns></returns>
Task<string> GetReadmeHtml(string owner, string name); Task<string> GetReadmeHtml(string owner, string name);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
Task<string> GetArchiveLink(string owner, string name);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <returns></returns>
Task<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat);
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <param name="reference">A valid Git reference.</param>
/// <returns></returns>
Task<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat, string reference);
/// <summary> /// <summary>
/// Creates a commit that creates a new file in a repository. /// Creates a commit that creates a new file in a repository.
/// </summary> /// </summary>
@@ -75,4 +115,14 @@ namespace Octokit
/// <param name="request">Information about the file to delete</param> /// <param name="request">Information about the file to delete</param>
Task DeleteFile(string owner, string name, string path, DeleteFileRequest request); Task DeleteFile(string owner, string name, string path, DeleteFileRequest request);
} }
public enum ArchiveFormat
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tarball")]
[Parameter(Value = "tarball")]
Tarball,
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zipball")]
[Parameter(Value = "zipball")]
Zipball
}
} }

View File

@@ -205,7 +205,9 @@ namespace Octokit
{ {
Ensure.ArgumentNotNull(request, "request"); Ensure.ArgumentNotNull(request, "request");
return ApiConnection.GetAll<Repository>(ApiUrls.AllPublicRepositories(), request.ToParametersDictionary()); var url = ApiUrls.AllPublicRepositories(request.Since);
return ApiConnection.GetAll<Repository>(url);
} }
/// <summary> /// <summary>

View File

@@ -74,6 +74,57 @@ namespace Octokit
return ApiConnection.GetHtml(ApiUrls.RepositoryReadme(owner, name), null); return ApiConnection.GetHtml(ApiUrls.RepositoryReadme(owner, name), null);
} }
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <returns></returns>
public Task<string> GetArchiveLink(string owner, string name)
{
return GetArchiveLink(owner, name, ArchiveFormat.Tarball, string.Empty);
}
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <returns></returns>
public Task<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat)
{
return GetArchiveLink(owner, name, archiveFormat, string.Empty);
}
/// <summary>
/// This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
/// Please make sure your HTTP framework is configured to follow redirects or you will need to use the
/// Location header to make a second GET request.
/// Note: For private repositories, these links are temporary and expire quickly.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#get-archive-link</remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="archiveFormat">The format of the archive. Can be either tarball or zipball</param>
/// <param name="reference">A valid Git reference.</param>
/// <returns></returns>
public Task<string> GetArchiveLink(string owner, string name, ArchiveFormat archiveFormat, string reference)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
return ApiConnection.GetRedirect(ApiUrls.RepositoryArchiveLink(owner, name, archiveFormat, reference));
}
/// <summary> /// <summary>
/// Creates a commit that creates a new file in a repository. /// Creates a commit that creates a new file in a repository.
/// </summary> /// </summary>

View File

@@ -67,6 +67,7 @@ namespace Octokit
StatusCode = response.StatusCode; StatusCode = response.StatusCode;
ApiError = GetApiErrorFromExceptionMessage(response); ApiError = GetApiErrorFromExceptionMessage(response);
HttpResponse = response;
} }
/// <summary> /// <summary>
@@ -97,6 +98,8 @@ namespace Octokit
StatusCode = statusCode; StatusCode = statusCode;
} }
public IResponse HttpResponse { get; private set; }
public override string Message public override string Message
{ {
get { return ApiErrorMessageSafe ?? "An error occurred with this API request"; } get { return ApiErrorMessageSafe ?? "An error occurred with this API request"; }

View File

@@ -0,0 +1,115 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Runtime.Serialization;
namespace Octokit
{
#if !NETFX_CORE
/// <summary>
/// Represents a failed 2FA challenge from the API
/// </summary>
[Serializable]
#endif
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors",
Justification = "These exceptions are specific to the GitHub API and not general purpose exceptions")]
public abstract class TwoFactorAuthorizationException : AuthorizationException
{
/// <summary>
/// Constructs an instance of TwoFactorRequiredException.
/// </summary>
/// <param name="twoFactorType">Expected 2FA response type</param>
/// <param name="innerException">The inner exception</param>
protected TwoFactorAuthorizationException(TwoFactorType twoFactorType, Exception innerException)
: base(HttpStatusCode.Unauthorized, innerException)
{
TwoFactorType = twoFactorType;
}
/// <summary>
/// Constructs an instance of TwoFactorRequiredException.
/// </summary>
/// <param name="response">The HTTP payload from the server</param>
/// <param name="twoFactorType">Expected 2FA response type</param>
protected TwoFactorAuthorizationException(IResponse response, TwoFactorType twoFactorType)
: base(response)
{
Debug.Assert(response != null && response.StatusCode == HttpStatusCode.Unauthorized,
"TwoFactorRequiredException status code should be 401");
TwoFactorType = twoFactorType;
}
/// <summary>
/// Constructs an instance of TwoFactorRequiredException.
/// </summary>
/// <param name="response">The HTTP payload from the server</param>
/// <param name="twoFactorType">Expected 2FA response type</param>
/// <param name="innerException">The inner exception</param>
protected TwoFactorAuthorizationException(IResponse response, TwoFactorType twoFactorType, Exception innerException)
: base(response, innerException)
{
Debug.Assert(response != null && response.StatusCode == HttpStatusCode.Unauthorized,
"TwoFactorRequiredException status code should be 401");
TwoFactorType = twoFactorType;
}
/// <summary>
/// Expected 2FA response type
/// </summary>
public TwoFactorType TwoFactorType { get; private set; }
#if !NETFX_CORE
/// <summary>
/// Constructs an instance of TwoFactorRequiredException.
/// </summary>
/// <param name="info">
/// The <see cref="SerializationInfo"/> that holds the
/// serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="StreamingContext"/> that contains
/// contextual information about the source or destination.
/// </param>
protected TwoFactorAuthorizationException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
if (info == null) return;
TwoFactorType = (TwoFactorType) (info.GetInt32("TwoFactorType"));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("TwoFactorType", TwoFactorType);
}
#endif
}
/// <summary>
/// Methods for receiving 2FA authentication codes
/// </summary>
public enum TwoFactorType
{
/// <summary>
/// No method configured
/// </summary>
None,
/// <summary>
/// Unknown method
/// </summary>
Unknown,
/// <summary>
/// Receive via SMS
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sms")]
Sms,
/// <summary>
/// Receive via application
/// </summary>
AuthenticatorApp
}
}

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Octokit namespace Octokit
@@ -13,23 +12,26 @@ namespace Octokit
#endif #endif
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors",
Justification = "These exceptions are specific to the GitHub API and not general purpose exceptions")] Justification = "These exceptions are specific to the GitHub API and not general purpose exceptions")]
public class TwoFactorChallengeFailedException : AuthorizationException public class TwoFactorChallengeFailedException : TwoFactorAuthorizationException
{ {
/// <summary> /// <summary>
/// Constructs an instance of TwoFactorChallengeFailedException /// Constructs an instance of TwoFactorChallengeFailedException
/// </summary> /// </summary>
public TwoFactorChallengeFailedException() : public TwoFactorChallengeFailedException() : base(TwoFactorType.None, null)
base(HttpStatusCode.Unauthorized, null)
{ {
} }
/// <summary> /// <summary>
/// Constructs an instance of TwoFactorChallengeFailedException /// Constructs an instance of TwoFactorChallengeFailedException
/// </summary> /// </summary>
/// <param name="authorizationCode">The authorization code that was incorrect</param>
/// <param name="innerException">The inner exception</param> /// <param name="innerException">The inner exception</param>
public TwoFactorChallengeFailedException(Exception innerException) public TwoFactorChallengeFailedException(
: base(HttpStatusCode.Unauthorized, innerException) string authorizationCode,
ApiException innerException)
: base(ParseTwoFactorType(innerException), innerException)
{ {
AuthorizationCode = authorizationCode;
} }
public override string Message public override string Message
@@ -37,9 +39,16 @@ namespace Octokit
get { return "The two-factor authentication code supplied is not correct"; } get { return "The two-factor authentication code supplied is not correct"; }
} }
public string AuthorizationCode { get; private set; }
static TwoFactorType ParseTwoFactorType(ApiException exception)
{
return exception == null ? TwoFactorType.None : Connection.ParseTwoFactorType(exception.HttpResponse);
}
#if !NETFX_CORE #if !NETFX_CORE
/// <summary> /// <summary>
/// Constructs an instance of TwoFactorChallengeFailedException /// Constructs an instance of TwoFactorChallengeFailedException.
/// </summary> /// </summary>
/// <param name="info"> /// <param name="info">
/// The <see cref="SerializationInfo"/> that holds the /// The <see cref="SerializationInfo"/> that holds the
@@ -52,6 +61,14 @@ namespace Octokit
protected TwoFactorChallengeFailedException(SerializationInfo info, StreamingContext context) protected TwoFactorChallengeFailedException(SerializationInfo info, StreamingContext context)
: base(info, context) : base(info, context)
{ {
if (info == null) return;
AuthorizationCode = info.GetString("AuthorizationCode");
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("AuthorizationCode", AuthorizationCode);
} }
#endif #endif
} }

View File

@@ -14,7 +14,7 @@ namespace Octokit
#endif #endif
[SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors",
Justification = "These exceptions are specific to the GitHub API and not general purpose exceptions")] Justification = "These exceptions are specific to the GitHub API and not general purpose exceptions")]
public class TwoFactorRequiredException : AuthorizationException public class TwoFactorRequiredException : TwoFactorAuthorizationException
{ {
/// <summary> /// <summary>
/// Constructs an instance of TwoFactorRequiredException. /// Constructs an instance of TwoFactorRequiredException.
@@ -27,10 +27,8 @@ namespace Octokit
/// Constructs an instance of TwoFactorRequiredException. /// Constructs an instance of TwoFactorRequiredException.
/// </summary> /// </summary>
/// <param name="twoFactorType">Expected 2FA response type</param> /// <param name="twoFactorType">Expected 2FA response type</param>
public TwoFactorRequiredException(TwoFactorType twoFactorType) public TwoFactorRequiredException(TwoFactorType twoFactorType) : base(twoFactorType, null)
: base(HttpStatusCode.Unauthorized, null)
{ {
TwoFactorType = twoFactorType;
} }
/// <summary> /// <summary>
@@ -38,12 +36,11 @@ namespace Octokit
/// </summary> /// </summary>
/// <param name="response">The HTTP payload from the server</param> /// <param name="response">The HTTP payload from the server</param>
/// <param name="twoFactorType">Expected 2FA response type</param> /// <param name="twoFactorType">Expected 2FA response type</param>
public TwoFactorRequiredException(IResponse response, TwoFactorType twoFactorType) : base(response) public TwoFactorRequiredException(IResponse response, TwoFactorType twoFactorType)
: base(response, twoFactorType)
{ {
Debug.Assert(response != null && response.StatusCode == HttpStatusCode.Unauthorized, Debug.Assert(response != null && response.StatusCode == HttpStatusCode.Unauthorized,
"TwoFactorRequiredException status code should be 401"); "TwoFactorRequiredException status code should be 401");
TwoFactorType = twoFactorType;
} }
public override string Message public override string Message
@@ -66,44 +63,7 @@ namespace Octokit
protected TwoFactorRequiredException(SerializationInfo info, StreamingContext context) protected TwoFactorRequiredException(SerializationInfo info, StreamingContext context)
: base(info, context) : base(info, context)
{ {
if (info == null) return;
TwoFactorType = (TwoFactorType) (info.GetInt32("TwoFactorType"));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("TwoFactorType", TwoFactorType);
} }
#endif #endif
/// <summary>
/// Expected 2FA response type
/// </summary>
public TwoFactorType TwoFactorType { get; private set; }
}
/// <summary>
/// Methods for receiving 2FA authentication codes
/// </summary>
public enum TwoFactorType
{
/// <summary>
/// No method configured
/// </summary>
None,
/// <summary>
/// Unknown method
/// </summary>
Unknown,
/// <summary>
/// Receive via SMS
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sms")]
Sms,
/// <summary>
/// Receive via application
/// </summary>
AuthenticatorApp
} }
} }

View File

@@ -23,7 +23,7 @@ namespace Octokit
/// the user agent for analytics purposes. /// the user agent for analytics purposes.
/// </param> /// </param>
public GitHubClient(ProductHeaderValue productInformation) public GitHubClient(ProductHeaderValue productInformation)
: this(new Connection(productInformation)) : this(new Connection(productInformation, GitHubApiUrl))
{ {
} }

View File

@@ -90,6 +90,14 @@ namespace Octokit
return connection.Get<T>(uri, null, null); return connection.Get<T>(uri, null, null);
} }
public static Task<IApiResponse<T>> GetRedirect<T>(this IConnection connection, Uri uri)
{
Ensure.ArgumentNotNull(connection, "connection");
Ensure.ArgumentNotNull(uri, "uri");
return connection.Get<T>(uri, null, null, false);
}
/// <summary> /// <summary>
/// Gets the API resource at the specified URI. /// Gets the API resource at the specified URI.
/// </summary> /// </summary>

View File

@@ -19,15 +19,23 @@ namespace Octokit
static readonly Uri _oauthAuthorize = new Uri("login/oauth/authorize", UriKind.Relative); static readonly Uri _oauthAuthorize = new Uri("login/oauth/authorize", UriKind.Relative);
static readonly Uri _oauthAccesToken = new Uri("login/oauth/access_token", UriKind.Relative); static readonly Uri _oauthAccesToken = new Uri("login/oauth/access_token", UriKind.Relative);
/// <summary>
/// Returns the <see cref="Uri"/> that returns all public repositories in
/// response to a GET request.
/// </summary>
public static Uri AllPublicRepositories()
{
return "/repositories".FormatUri();
}
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that returns all public repositories in /// Returns the <see cref="Uri"/> that returns all public repositories in
/// response to a GET request. /// response to a GET request.
/// </summary> /// </summary>
/// <returns></returns> /// <param name="since">The integer ID of the last Repository that youve seen.</param>
public static Uri AllPublicRepositories() public static Uri AllPublicRepositories(long since)
{ {
return "/repositories".FormatUri(); return "/repositories?since={0}".FormatUri(since);
} }
/// <summary> /// <summary>
@@ -773,7 +781,7 @@ namespace Octokit
} }
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> for the forks of a given gist. /// Returns the <see cref="Uri"/> for the forks for the specified gist.
/// </summary> /// </summary>
/// <param name="id">The id of the gist</param> /// <param name="id">The id of the gist</param>
public static Uri ForkGist(string id) public static Uri ForkGist(string id)
@@ -824,6 +832,15 @@ namespace Octokit
return "gists/{0}/comments".FormatUri(gistId); return "gists/{0}/comments".FormatUri(gistId);
} }
/// <summary>
/// Returns the <see cref="Uri"/> for the commits for the specified gist.
/// </summary>
/// <param name="id">The id of the gist</param>
public static Uri GistCommits(string id)
{
return "gists/{0}/commits".FormatUri(id);
}
/// <summary> /// <summary>
/// Returns the <see cref="Uri"/> that returns the specified pull request. /// Returns the <see cref="Uri"/> that returns the specified pull request.
/// </summary> /// </summary>
@@ -1508,5 +1525,10 @@ namespace Octokit
{ {
return "repos/{0}/{1}/contents/{2}".FormatUri(owner, name, path); return "repos/{0}/{1}/contents/{2}".FormatUri(owner, name, path);
} }
public static Uri RepositoryArchiveLink(string owner, string name, ArchiveFormat archiveFormat, string reference)
{
return "repos/{0}/{1}/{2}/{3}".FormatUri(owner, name, archiveFormat.ToParameter(), reference);
}
} }
} }

View File

@@ -199,6 +199,33 @@ namespace Octokit
return response.Body; return response.Body;
} }
/// <summary>
/// Creates a new API resource in the list at the specified URI.
/// </summary>
/// <typeparam name="T">The API resource's type.</typeparam>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="data">Object that describes the new API resource; this will be serialized and used as the request's body</param>
/// <param name="accepts">Accept header to use for the API request</param>
/// <param name="contentType">Content type of the API request</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Authentication Code</param>
/// <returns>The created API resource.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
public async Task<T> Post<T>(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNull(uri, "uri");
Ensure.ArgumentNotNull(data, "data");
Ensure.ArgumentNotNull(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
var response = await Connection.Post<T>(
uri,
data,
accepts,
contentType,
twoFactorAuthenticationCode).ConfigureAwait(false);
return response.Body;
}
public async Task<T> Post<T>(Uri uri, object data, string accepts, string contentType, TimeSpan timeout) public async Task<T> Post<T>(Uri uri, object data, string accepts, string contentType, TimeSpan timeout)
{ {
Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(uri, "uri");
@@ -345,6 +372,19 @@ namespace Octokit
return Connection.Delete(uri); return Connection.Delete(uri);
} }
/// <summary>
/// Deletes the API object at the specified URI.
/// </summary>
/// <param name="uri">URI of the API resource to delete</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Code</param>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
public Task Delete(Uri uri, string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNull(uri, "uri");
return Connection.Delete(uri, twoFactorAuthenticationCode);
}
/// <summary> /// <summary>
/// Deletes the API object at the specified URI. /// Deletes the API object at the specified URI.
/// </summary> /// </summary>
@@ -359,6 +399,28 @@ namespace Octokit
return Connection.Delete(uri, data); return Connection.Delete(uri, data);
} }
/// <summary>
/// Executes a GET to the API object at the specified URI. This operation is appropriate for
/// API calls which wants to return the redirect URL.
/// It expects the API to respond with a 302 Found.
/// </summary>
/// <param name="uri">URI of the API resource to get</param>
/// <returns>The URL returned by the API in the Location header</returns>
/// <exception cref="ApiException">Thrown when an API error occurs, or the API does not respond with a 302 Found</exception>
public async Task<string> GetRedirect(Uri uri)
{
Ensure.ArgumentNotNull(uri, "uri");
var response = await Connection.GetRedirect<string>(uri);
if (response.HttpResponse.StatusCode == HttpStatusCode.Redirect)
{
return response.HttpResponse.Headers["Location"];
}
throw new ApiException("Redirect Operation expect status code of Redirect.",
response.HttpResponse.StatusCode);
}
/// <summary> /// <summary>
/// Executes a GET to the API object at the specified URI. This operation is appropriate for /// Executes a GET to the API object at the specified URI. This operation is appropriate for
/// API calls which queue long running calculations. /// API calls which queue long running calculations.

View File

@@ -142,6 +142,24 @@ namespace Octokit
return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, CancellationToken.None); return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, CancellationToken.None);
} }
/// <summary>
/// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The type to map the response to</typeparam>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <param name="accepts">Specifies accepted response media types.</param>
/// <param name="allowAutoRedirect">To follow redirect links automatically or not</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
public Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, bool allowAutoRedirect)
{
Ensure.ArgumentNotNull(uri, "uri");
return SendData<T>(uri.ApplyParameters(parameters), HttpMethod.Get, null, accepts, null, CancellationToken.None, allowAutoRedirect: allowAutoRedirect);
}
public Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken) public Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, CancellationToken cancellationToken)
{ {
Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(uri, "uri");
@@ -205,6 +223,27 @@ namespace Octokit
return SendData<T>(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None); return SendData<T>(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None);
} }
/// <summary>
/// Performs an asynchronous HTTP POST request.
/// Attempts to map the response body to an object of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The type to map the response to</typeparam>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="body">The object to serialize as the body of the request</param>
/// <param name="accepts">Specifies accepted response media types.</param>
/// <param name="contentType">Specifies the media type of the request body</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Authentication Code</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
public Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType, string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNull(uri, "uri");
Ensure.ArgumentNotNull(body, "body");
Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, "twoFactorAuthenticationCode");
return SendData<T>(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None, twoFactorAuthenticationCode);
}
public Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType, TimeSpan timeout) public Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType, TimeSpan timeout)
{ {
Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(uri, "uri");
@@ -281,7 +320,8 @@ namespace Octokit
string contentType, string contentType,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string twoFactorAuthenticationCode = null, string twoFactorAuthenticationCode = null,
Uri baseAddress = null) Uri baseAddress = null,
bool allowAutoRedirect = true)
{ {
Ensure.ArgumentNotNull(uri, "uri"); Ensure.ArgumentNotNull(uri, "uri");
@@ -290,6 +330,7 @@ namespace Octokit
Method = method, Method = method,
BaseAddress = baseAddress ?? BaseAddress, BaseAddress = baseAddress ?? BaseAddress,
Endpoint = uri, Endpoint = uri,
AllowAutoRedirect = allowAutoRedirect,
}; };
return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request); return SendDataInternal<T>(body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode, request);
@@ -374,6 +415,27 @@ namespace Octokit
return response.HttpResponse.StatusCode; return response.HttpResponse.StatusCode;
} }
/// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Code</param>
/// <returns>The returned <seealso cref="HttpStatusCode"/></returns>
public async Task<HttpStatusCode> Delete(Uri uri, string twoFactorAuthenticationCode)
{
Ensure.ArgumentNotNull(uri, "uri");
var response = await SendData<object>(
uri,
HttpMethod.Delete,
null,
null,
null,
CancellationToken.None,
twoFactorAuthenticationCode);
return response.HttpResponse.StatusCode;
}
/// <summary> /// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response. /// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary> /// </summary>
@@ -502,9 +564,9 @@ namespace Octokit
: new ForbiddenException(response); : new ForbiddenException(response);
} }
static TwoFactorType ParseTwoFactorType(IResponse restResponse) internal static TwoFactorType ParseTwoFactorType(IResponse restResponse)
{ {
if (restResponse.Headers == null || !restResponse.Headers.Any()) return TwoFactorType.None; if (restResponse == null || restResponse.Headers == null || !restResponse.Headers.Any()) return TwoFactorType.None;
var otpHeader = restResponse.Headers.FirstOrDefault(header => var otpHeader = restResponse.Headers.FirstOrDefault(header =>
header.Key.Equals("X-GitHub-OTP", StringComparison.OrdinalIgnoreCase)); header.Key.Equals("X-GitHub-OTP", StringComparison.OrdinalIgnoreCase));
if (String.IsNullOrEmpty(otpHeader.Value)) return TwoFactorType.None; if (String.IsNullOrEmpty(otpHeader.Value)) return TwoFactorType.None;

View File

@@ -50,15 +50,21 @@ namespace Octokit.Internal
httpOptions.Proxy = _webProxy; httpOptions.Proxy = _webProxy;
} }
var http = new HttpClient(httpOptions) var http = new HttpClient(httpOptions);
var cancellationTokenForRequest = cancellationToken;
if (request.Timeout != TimeSpan.Zero)
{ {
BaseAddress = request.BaseAddress, var timeoutCancellation = new CancellationTokenSource(request.Timeout);
Timeout = request.Timeout var unifiedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token);
};
cancellationTokenForRequest = unifiedCancellationToken.Token;
}
using (var requestMessage = BuildRequestMessage(request)) using (var requestMessage = BuildRequestMessage(request))
{ {
// Make the request // Make the request
var responseMessage = await http.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken) var responseMessage = await http.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationTokenForRequest)
.ConfigureAwait(false); .ConfigureAwait(false);
return await BuildResponse(responseMessage).ConfigureAwait(false); return await BuildResponse(responseMessage).ConfigureAwait(false);
} }
@@ -76,14 +82,14 @@ namespace Octokit.Internal
{ {
contentType = GetContentMediaType(responseMessage.Content); contentType = GetContentMediaType(responseMessage.Content);
// We added support for downloading images. Let's constrain this appropriately. // We added support for downloading images and zip-files. Let's constrain this appropriately.
if (contentType == null || !contentType.StartsWith("image/")) if (contentType != null && (contentType.StartsWith("image/") || contentType.Equals("application/zip", StringComparison.OrdinalIgnoreCase)))
{ {
responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); responseBody = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
} }
else else
{ {
responseBody = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false); responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
} }
} }
} }
@@ -101,7 +107,8 @@ namespace Octokit.Internal
HttpRequestMessage requestMessage = null; HttpRequestMessage requestMessage = null;
try try
{ {
requestMessage = new HttpRequestMessage(request.Method, request.Endpoint); var fullUri = new Uri(request.BaseAddress, request.Endpoint);
requestMessage = new HttpRequestMessage(request.Method, fullUri);
foreach (var header in request.Headers) foreach (var header in request.Headers)
{ {
requestMessage.Headers.Add(header.Key, header.Value); requestMessage.Headers.Add(header.Key, header.Value);

View File

@@ -123,6 +123,19 @@ namespace Octokit
/// <exception cref="ApiException">Thrown when an API error occurs.</exception> /// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<T> Post<T>(Uri uri, object data, string accepts, string contentType); Task<T> Post<T>(Uri uri, object data, string accepts, string contentType);
/// <summary>
/// Creates a new API resource in the list at the specified URI.
/// </summary>
/// <typeparam name="T">The API resource's type.</typeparam>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="data">Object that describes the new API resource; this will be serialized and used as the request's body</param>
/// <param name="accepts">Accept header to use for the API request</param>
/// <param name="contentType">Content type of the API request</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Authentication Code</param>
/// <returns>The created API resource.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<T> Post<T>(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// Creates a new API resource in the list at the specified URI. /// Creates a new API resource in the list at the specified URI.
/// </summary> /// </summary>
@@ -212,6 +225,14 @@ namespace Octokit
/// <returns>A <see cref="Task"/> for the request's execution.</returns> /// <returns>A <see cref="Task"/> for the request's execution.</returns>
Task Delete(Uri uri); Task Delete(Uri uri);
/// <summary>
/// Deletes the API object at the specified URI.
/// </summary>
/// <param name="uri">URI of the API resource to delete</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Code</param>
/// <returns>A <see cref="Task"/> for the request's execution.</returns>
Task Delete(Uri uri, string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// Deletes the API object at the specified URI. /// Deletes the API object at the specified URI.
/// </summary> /// </summary>
@@ -220,6 +241,16 @@ namespace Octokit
/// <returns>A <see cref="Task"/> for the request's execution.</returns> /// <returns>A <see cref="Task"/> for the request's execution.</returns>
Task Delete(Uri uri, object data); Task Delete(Uri uri, object data);
/// <summary>
/// Executes a GET to the API object at the specified URI. This operation is appropriate for
/// API calls which wants to return the redirect URL.
/// It expects the API to respond with a 302 Found.
/// </summary>
/// <param name="uri">URI of the API resource to get</param>
/// <returns>The URL returned by the API in the Location header</returns>
/// <exception cref="ApiException">Thrown when an API error occurs, or the API does not respond with a 302 Found</exception>
Task<string> GetRedirect(Uri uri);
/// <summary> /// <summary>
/// Executes a GET to the API object at the specified URI. This operation is appropriate for /// Executes a GET to the API object at the specified URI. This operation is appropriate for
/// API calls which queue long running calculations. /// API calls which queue long running calculations.

View File

@@ -32,6 +32,19 @@ namespace Octokit
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")]
Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts); Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts);
/// <summary>
/// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The type to map the response to</typeparam>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <param name="accepts">Specifies accepted response media types.</param>
/// <param name="allowAutoRedirect">To follow redirect links automatically or not</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Get")]
Task<IApiResponse<T>> Get<T>(Uri uri, IDictionary<string, string> parameters, string accepts, bool allowAutoRedirect);
/// <summary> /// <summary>
/// Performs an asynchronous HTTP GET request. /// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/> /// Attempts to map the response to an object of type <typeparamref name="T"/>
@@ -92,6 +105,19 @@ namespace Octokit
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns> /// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType); Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType);
/// <summary>
/// Performs an asynchronous HTTP POST request.
/// Attempts to map the response body to an object of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T">The type to map the response to</typeparam>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="body">The object to serialize as the body of the request</param>
/// <param name="accepts">Specifies accepted response media types.</param>
/// <param name="contentType">Specifies the media type of the request body</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Authentication Code</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
Task<IApiResponse<T>> Post<T>(Uri uri, object body, string accepts, string contentType, string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// Performs an asynchronous HTTP POST request. /// Performs an asynchronous HTTP POST request.
/// Attempts to map the response body to an object of type <typeparamref name="T"/> /// Attempts to map the response body to an object of type <typeparamref name="T"/>
@@ -171,6 +197,14 @@ namespace Octokit
/// <returns>The returned <seealso cref="HttpStatusCode"/></returns> /// <returns>The returned <seealso cref="HttpStatusCode"/></returns>
Task<HttpStatusCode> Delete(Uri uri); Task<HttpStatusCode> Delete(Uri uri);
/// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="twoFactorAuthenticationCode">Two Factor Code</param>
/// <returns>The returned <seealso cref="HttpStatusCode"/></returns>
Task<HttpStatusCode> Delete(Uri uri, string twoFactorAuthenticationCode);
/// <summary> /// <summary>
/// Performs an asynchronous HTTP DELETE request that expects an empty response. /// Performs an asynchronous HTTP DELETE request that expects an empty response.
/// </summary> /// </summary>

View File

@@ -56,7 +56,7 @@ namespace Octokit
/// <summary> /// <summary>
/// Line index in the diff that was commented on. /// Line index in the diff that was commented on.
/// </summary> /// </summary>
public int Position { get; protected set; } public int? Position { get; protected set; }
/// <summary> /// <summary>
/// The line number in the file that was commented on. /// The line number in the file that was commented on.

View File

@@ -5,7 +5,7 @@ using System.Globalization;
namespace Octokit namespace Octokit
{ {
/// <summary> /// <summary>
/// User by <see cref="GistHistory"/> to indicate the level of change. /// Used by <see cref="GistHistory"/> to indicate the level of change.
/// </summary> /// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerDisplay("{DebuggerDisplay,nq}")]
public class GistChangeStatus public class GistChangeStatus

View File

@@ -14,7 +14,7 @@ namespace Octokit
{ {
public PullRequestFile() { } public PullRequestFile() { }
public PullRequestFile(string sha, string fileName, string status, int additions, int deletions, int changes, Uri blobUri, Uri rawUri, Uri contentsUri, string patch) public PullRequestFile(string sha, string fileName, string status, int additions, int deletions, int changes, Uri blobUrl, Uri rawUrl, Uri contentsUrl, string patch)
{ {
Sha = sha; Sha = sha;
FileName = fileName; FileName = fileName;
@@ -22,9 +22,9 @@ namespace Octokit
Additions = additions; Additions = additions;
Deletions = deletions; Deletions = deletions;
Changes = changes; Changes = changes;
BlobUri = blobUri; BlobUrl = blobUrl;
RawUri = rawUri; RawUrl = rawUrl;
ContentsUri = contentsUri; ContentsUrl = contentsUrl;
Patch = patch; Patch = patch;
} }
@@ -35,9 +35,9 @@ namespace Octokit
public int Additions { get; protected set; } public int Additions { get; protected set; }
public int Deletions { get; protected set; } public int Deletions { get; protected set; }
public int Changes { get; protected set; } public int Changes { get; protected set; }
public Uri BlobUri { get; protected set; } public Uri BlobUrl { get; protected set; }
public Uri RawUri { get; protected set; } public Uri RawUrl { get; protected set; }
public Uri ContentsUri { get; protected set; } public Uri ContentsUrl { get; protected set; }
public string Patch { get; protected set; } public string Patch { get; protected set; }
internal string DebuggerDisplay internal string DebuggerDisplay

View File

@@ -392,6 +392,7 @@
<Compile Include="Models\Response\LicenseMetadata.cs" /> <Compile Include="Models\Response\LicenseMetadata.cs" />
<Compile Include="Models\Response\PullRequestFile.cs" /> <Compile Include="Models\Response\PullRequestFile.cs" />
<Compile Include="Models\Request\PublicRepositoryRequest.cs" /> <Compile Include="Models\Request\PublicRepositoryRequest.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@@ -407,6 +407,7 @@
<Compile Include="Models\Response\PullRequestFile.cs" /> <Compile Include="Models\Response\PullRequestFile.cs" />
<Compile Include="Models\Request\PublicRepositoryRequest.cs" /> <Compile Include="Models\Request\PublicRepositoryRequest.cs" />
<Compile Include="Models\Request\RepositoryHooksPingRequest.cs" /> <Compile Include="Models\Request\RepositoryHooksPingRequest.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
<Compile Include="Models\Request\RepositoryHookTestRequest.cs" /> <Compile Include="Models\Request\RepositoryHookTestRequest.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />

View File

@@ -400,6 +400,7 @@
<Compile Include="Models\Response\PullRequestFile.cs" /> <Compile Include="Models\Response\PullRequestFile.cs" />
<Compile Include="Models\Request\PublicRepositoryRequest.cs" /> <Compile Include="Models\Request\PublicRepositoryRequest.cs" />
<Compile Include="Models\Request\RepositoryHooksPingRequest.cs" /> <Compile Include="Models\Request\RepositoryHooksPingRequest.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
<Compile Include="Models\Request\RepositoryHookTestRequest.cs" /> <Compile Include="Models\Request\RepositoryHookTestRequest.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.MonoTouch.CSharp.targets" />

View File

@@ -390,6 +390,7 @@
<Compile Include="Models\Response\LicenseMetadata.cs" /> <Compile Include="Models\Response\LicenseMetadata.cs" />
<Compile Include="Models\Response\PullRequestFile.cs" /> <Compile Include="Models\Response\PullRequestFile.cs" />
<Compile Include="Models\Request\PublicRepositoryRequest.cs" /> <Compile Include="Models\Request\PublicRepositoryRequest.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml"> <CodeAnalysisDictionary Include="..\CustomDictionary.xml">

View File

@@ -394,6 +394,7 @@
<Compile Include="Models\Response\LicenseMetadata.cs" /> <Compile Include="Models\Response\LicenseMetadata.cs" />
<Compile Include="Models\Response\PullRequestFile.cs" /> <Compile Include="Models\Response\PullRequestFile.cs" />
<Compile Include="Models\Request\PublicRepositoryRequest.cs" /> <Compile Include="Models\Request\PublicRepositoryRequest.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml"> <CodeAnalysisDictionary Include="..\CustomDictionary.xml">

View File

@@ -73,6 +73,7 @@
<Compile Include="Clients\RepositoryContentsClient.cs" /> <Compile Include="Clients\RepositoryContentsClient.cs" />
<Compile Include="Exceptions\PrivateRepositoryQuotaExceededException.cs" /> <Compile Include="Exceptions\PrivateRepositoryQuotaExceededException.cs" />
<Compile Include="Exceptions\RepositoryExistsException.cs" /> <Compile Include="Exceptions\RepositoryExistsException.cs" />
<Compile Include="Exceptions\TwoFactorAuthorizationException.cs" />
<Compile Include="Helpers\ApiErrorExtensions.cs" /> <Compile Include="Helpers\ApiErrorExtensions.cs" />
<Compile Include="Helpers\ApiUrls.Authorizations.cs" /> <Compile Include="Helpers\ApiUrls.Authorizations.cs" />
<Compile Include="Helpers\ApiUrls.Keys.cs" /> <Compile Include="Helpers\ApiUrls.Keys.cs" />

View File

@@ -1,3 +1,21 @@
### New in 0.11.0 (released 2015/05/10)
* New: Added overload to `IRepositoryClient.GetAllPublic` specifying a `since` parameter - #774 via @alfhenrik
* New: Added `IGistsClient.GetAllCommits` and `IGistsClient.GetAllForks` implementations - #542 via @haagenson, #794 via @shiftkey
* New: Added `IRepositoryContentsClient.GetArchiveLink` for getting archived code - #765 via @alfhenrik
* Fixed: `PullRequestFile` properties were not serialized correctly - #789 via @thedillonb
* Fixed: Allow to download zip-attachments - #792 via @csware
### New in 0.10.0 (released 2015/04/22)
* Fixed: renamed methods to follow `GetAll` convention - #771 via @alfhenrik
* Fixed: helper functions and cleanup to make using Authorization API easier to consume - #786 via @haacked
**Breaking Changes:**
- As part of #771 there were many method which were returning collections
but the method name made it unclear. You might think that it wasn't much, but
you'd be wrong. So if you have a method that no longer compile,
it is likely that you need to set the prefix to `GetAll` to re-disocver that API.
- `CommitComment.Position` is now a nullable `int` to prevent serialization issues.
### New in 0.9.0 (released 2015/04/04) ### New in 0.9.0 (released 2015/04/04)
* New: added `PullRequest.Files` APIs - #752 via @alfhenrik * New: added `PullRequest.Files` APIs - #752 via @alfhenrik
* Fixed: `PullRequestRequest` now supports `SortDirection` and `SortProperty` - #752 via @alfhenrik * Fixed: `PullRequestRequest` now supports `SortDirection` and `SortProperty` - #752 via @alfhenrik

View File

@@ -3,11 +3,11 @@ using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
[assembly: AssemblyProductAttribute("Octokit")] [assembly: AssemblyProductAttribute("Octokit")]
[assembly: AssemblyVersionAttribute("0.9.0")] [assembly: AssemblyVersionAttribute("0.11.0")]
[assembly: AssemblyFileVersionAttribute("0.9.0")] [assembly: AssemblyFileVersionAttribute("0.11.0")]
[assembly: ComVisibleAttribute(false)] [assembly: ComVisibleAttribute(false)]
namespace System { namespace System {
internal static class AssemblyVersionInformation { internal static class AssemblyVersionInformation {
internal const string Version = "0.9.0"; internal const string Version = "0.11.0";
} }
} }