diff --git a/Octokit.Reactive/Clients/IObservableIssueTimelineClient.cs b/Octokit.Reactive/Clients/IObservableIssueTimelineClient.cs
new file mode 100644
index 0000000000..251fa7c851
--- /dev/null
+++ b/Octokit.Reactive/Clients/IObservableIssueTimelineClient.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace Octokit.Reactive
+{
+ ///
+ /// A client for GitHub's Issue Timeline API.
+ ///
+ ///
+ /// See the Issue Timeline API documentation for more information.
+ ///
+ public interface IObservableIssueTimelineClient
+ {
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ IObservable GetAllForIssue(string owner, string repo, int number);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ /// Options for changing the API response
+ IObservable GetAllForIssue(string owner, string repo, int number, ApiOptions options);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ IObservable GetAllForIssue(int repositoryId, int number);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ /// Options for changing the API response
+ IObservable GetAllForIssue(int repositoryId, int number, ApiOptions options);
+ }
+}
diff --git a/Octokit.Reactive/Clients/IObservableIssuesClient.cs b/Octokit.Reactive/Clients/IObservableIssuesClient.cs
index 9637d9b68e..0e9bd4478d 100644
--- a/Octokit.Reactive/Clients/IObservableIssuesClient.cs
+++ b/Octokit.Reactive/Clients/IObservableIssuesClient.cs
@@ -39,6 +39,11 @@ public interface IObservableIssuesClient
///
IObservableIssueCommentsClient Comment { get; }
+ ///
+ /// Client for reading the timeline of events for an issue
+ ///
+ IObservableIssueTimelineClient Timeline { get; }
+
///
/// Gets a single Issue by number.
///
diff --git a/Octokit.Reactive/Clients/ObservableIssueTimelineClient.cs b/Octokit.Reactive/Clients/ObservableIssueTimelineClient.cs
new file mode 100644
index 0000000000..9e53b48678
--- /dev/null
+++ b/Octokit.Reactive/Clients/ObservableIssueTimelineClient.cs
@@ -0,0 +1,88 @@
+using System;
+using Octokit.Reactive.Internal;
+
+namespace Octokit.Reactive
+{
+ ///
+ /// A client for GitHub's Issue Timeline API.
+ ///
+ ///
+ /// See the Issue Timeline API documentation for more information.
+ ///
+ public class ObservableIssueTimelineClient : IObservableIssueTimelineClient
+ {
+ readonly IConnection _connection;
+
+ public ObservableIssueTimelineClient(IGitHubClient client)
+ {
+ Ensure.ArgumentNotNull(client, "client");
+
+ _connection = client.Connection;
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ public IObservable GetAllForIssue(string owner, string repo, int number)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
+ Ensure.ArgumentNotNullOrEmptyString(repo, "repo");
+
+ return GetAllForIssue(owner, repo, number, ApiOptions.None);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ /// Options for changing the API response
+ public IObservable GetAllForIssue(string owner, string repo, int number, ApiOptions options)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
+ Ensure.ArgumentNotNullOrEmptyString(repo, "repo");
+ Ensure.ArgumentNotNull(options, "options");
+
+ return _connection.GetAndFlattenAllPages(ApiUrls.IssueTimeline(owner, repo, number), null, AcceptHeaders.IssueTimelineApiPreview, options);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ public IObservable GetAllForIssue(int repositoryId, int number)
+ {
+ return GetAllForIssue(repositoryId, number, ApiOptions.None);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ /// Options for changing the API response
+ public IObservable GetAllForIssue(int repositoryId, int number, ApiOptions options)
+ {
+ Ensure.ArgumentNotNull(options, "options");
+
+ return _connection.GetAndFlattenAllPages(ApiUrls.IssueTimeline(repositoryId, number), null, AcceptHeaders.IssueTimelineApiPreview, options);
+ }
+ }
+}
diff --git a/Octokit.Reactive/Clients/ObservableIssuesClient.cs b/Octokit.Reactive/Clients/ObservableIssuesClient.cs
index 5156fe68b7..4e715ad53c 100644
--- a/Octokit.Reactive/Clients/ObservableIssuesClient.cs
+++ b/Octokit.Reactive/Clients/ObservableIssuesClient.cs
@@ -43,6 +43,11 @@ public class ObservableIssuesClient : IObservableIssuesClient
///
public IObservableMilestonesClient Milestone { get; private set; }
+ ///
+ /// Client for reading the timeline of events for an issue
+ ///
+ public IObservableIssueTimelineClient Timeline { get; private set; }
+
public ObservableIssuesClient(IGitHubClient client)
{
Ensure.ArgumentNotNull(client, "client");
@@ -54,6 +59,7 @@ public ObservableIssuesClient(IGitHubClient client)
Labels = new ObservableIssuesLabelsClient(client);
Milestone = new ObservableMilestonesClient(client);
Comment = new ObservableIssueCommentsClient(client);
+ Timeline = new ObservableIssueTimelineClient(client);
}
///
diff --git a/Octokit.Reactive/Octokit.Reactive-Mono.csproj b/Octokit.Reactive/Octokit.Reactive-Mono.csproj
index 0964b1ffa7..c96ce61bfb 100644
--- a/Octokit.Reactive/Octokit.Reactive-Mono.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-Mono.csproj
@@ -192,6 +192,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
index 6a5e5193d4..2b555cc715 100644
--- a/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-MonoAndroid.csproj
@@ -208,6 +208,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
index dcef4ea5f3..0431a56579 100644
--- a/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
+++ b/Octokit.Reactive/Octokit.Reactive-Monotouch.csproj
@@ -204,6 +204,8 @@
+
+
diff --git a/Octokit.Reactive/Octokit.Reactive.csproj b/Octokit.Reactive/Octokit.Reactive.csproj
index cf8b2786ac..ae1cdaf97e 100644
--- a/Octokit.Reactive/Octokit.Reactive.csproj
+++ b/Octokit.Reactive/Octokit.Reactive.csproj
@@ -93,6 +93,7 @@
+
@@ -109,6 +110,7 @@
+
diff --git a/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs b/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs
new file mode 100644
index 0000000000..af18d72f67
--- /dev/null
+++ b/Octokit.Tests.Integration/Clients/IssueTimelineClientTests.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Threading.Tasks;
+using Octokit.Tests.Integration.Helpers;
+using Xunit;
+
+namespace Octokit.Tests.Integration.Clients
+{
+ public class IssueTimelineClientTests :IDisposable
+ {
+ private readonly IIssueTimelineClient _issueTimelineClient;
+ private readonly IIssuesClient _issuesClient;
+ private readonly RepositoryContext _context;
+
+ public IssueTimelineClientTests()
+ {
+ var github = Helper.GetAuthenticatedClient();
+
+ _issueTimelineClient = github.Issue.Timeline;
+ _issuesClient = github.Issue;
+
+ var repoName = Helper.MakeNameWithTimestamp("public-repo");
+
+ _context = github.CreateRepositoryContext(new NewRepository(repoName)).Result;
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssue()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.RepositoryOwner, _context.RepositoryName, newIssue);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ Assert.Empty(timelineEventInfos);
+
+ var closed = await _issuesClient.Update(_context.RepositoryOwner, _context.RepositoryName, issue.Number, new IssueUpdate() { State = ItemState.Closed });
+ Assert.NotNull(closed);
+
+ timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(EventInfoState.Closed, timelineEventInfos[0].Event);
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssueWithApiOptions()
+ {
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue("octokit", "octokit.net", 1115);
+ Assert.NotEmpty(timelineEventInfos);
+ Assert.NotEqual(1, timelineEventInfos.Count);
+
+ var pageOptions = new ApiOptions
+ {
+ PageSize = 1,
+ PageCount = 1,
+ StartPage = 1
+ };
+
+ timelineEventInfos = await _issueTimelineClient.GetAllForIssue("octokit", "octokit.net", 1115, pageOptions);
+ Assert.NotEmpty(timelineEventInfos);
+ Assert.Equal(1, timelineEventInfos.Count);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeRenameEvent()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.RepositoryOwner, _context.RepositoryName, newIssue);
+
+ var renamed = await _issuesClient.Update(_context.Repository.Id, issue.Number, new IssueUpdate { Title = "A test issue" });
+ Assert.NotNull(renamed);
+ Assert.Equal("A test issue", renamed.Title);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal("a test issue", timelineEventInfos[0].Rename.From);
+ Assert.Equal("A test issue", timelineEventInfos[0].Rename.To);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeCrossReferenceEvent()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.RepositoryOwner, _context.RepositoryName, newIssue);
+
+ newIssue = new NewIssue("another test issue") { Body = "Another new unassigned issue referencing the first new issue in #" + issue.Number };
+ var anotherNewIssue = await _issuesClient.Create(_context.Repository.Id, newIssue);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(anotherNewIssue.Id, timelineEventInfos[0].Source.Id);
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssueByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.Repository.Id, newIssue);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.Repository.Id, issue.Number);
+ Assert.Empty(timelineEventInfos);
+
+ var closed = await _issuesClient.Update(_context.Repository.Id, issue.Number, new IssueUpdate() { State = ItemState.Closed });
+ Assert.NotNull(closed);
+
+ timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.Repository.Id, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(EventInfoState.Closed, timelineEventInfos[0].Event);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeRenameEventByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.Repository.Id, newIssue);
+
+ var renamed = await _issuesClient.Update(_context.Repository.Id, issue.Number, new IssueUpdate { Title = "A test issue" });
+ Assert.NotNull(renamed);
+ Assert.Equal("A test issue", renamed.Title);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.Repository.Id, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal("a test issue", timelineEventInfos[0].Rename.From);
+ Assert.Equal("A test issue", timelineEventInfos[0].Rename.To);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeCrossReferenceEventByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var issue = await _issuesClient.Create(_context.Repository.Id, newIssue);
+
+ newIssue = new NewIssue("another test issue") { Body = "Another new unassigned issue referencing the first new issue in #" + issue.Number };
+ var anotherNewIssue = await _issuesClient.Create(_context.Repository.Id, newIssue);
+
+ var timelineEventInfos = await _issueTimelineClient.GetAllForIssue(_context.Repository.Id, issue.Number);
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(anotherNewIssue.Id, timelineEventInfos[0].Source.Id);
+ }
+
+ public void Dispose()
+ {
+ _context.Dispose();
+ }
+ }
+}
diff --git a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
index 4681be2473..62a1cd4090 100644
--- a/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
+++ b/Octokit.Tests.Integration/Octokit.Tests.Integration.csproj
@@ -87,6 +87,7 @@
+
@@ -153,6 +154,7 @@
+
diff --git a/Octokit.Tests.Integration/Reactive/ObservableIssueTimelineClientTests.cs b/Octokit.Tests.Integration/Reactive/ObservableIssueTimelineClientTests.cs
new file mode 100644
index 0000000000..7cfb66a887
--- /dev/null
+++ b/Octokit.Tests.Integration/Reactive/ObservableIssueTimelineClientTests.cs
@@ -0,0 +1,158 @@
+using Octokit.Tests.Integration.Helpers;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Octokit.Reactive;
+using Xunit;
+
+namespace Octokit.Tests.Integration.Reactive
+{
+ public class ObservableIssueTimelineClientTests
+ {
+ private readonly RepositoryContext _context;
+ private readonly ObservableGitHubClient _client;
+
+ public ObservableIssueTimelineClientTests()
+ {
+ var github = Helper.GetAuthenticatedClient();
+
+ _client = new ObservableGitHubClient(github);
+
+ var reponame = Helper.MakeNameWithTimestamp("public-repo");
+ _context = github.CreateRepositoryContext(new NewRepository(reponame)).Result;
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssue()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Empty(timelineEventInfos);
+
+ observable = _client.Issue.Update(_context.Repository.Id, issue.Number, new IssueUpdate { State = ItemState.Closed });
+ var closed = await observable;
+ Assert.NotNull(closed);
+
+ observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(EventInfoState.Closed, timelineEventInfos[0].Event);
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssueWithApiOptions()
+ {
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue("octokit", "octokit.net", 1115);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.NotEmpty(timelineEventInfos);
+ Assert.NotEqual(1, timelineEventInfos.Count);
+
+ var pageOptions = new ApiOptions
+ {
+ PageSize = 1,
+ PageCount = 1,
+ StartPage = 1
+ };
+ observableTimeline = _client.Issue.Timeline.GetAllForIssue("octokit", "octokit.net", 1115, pageOptions);
+ timelineEventInfos = await observableTimeline.ToList();
+ Assert.NotEmpty(timelineEventInfos);
+ Assert.Equal(1, timelineEventInfos.Count);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeRenameEvent()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ observable = _client.Issue.Update(_context.Repository.Id, issue.Number, new IssueUpdate { Title = "A test issue" });
+ var renamed = await observable;
+ Assert.NotNull(renamed);
+ Assert.Equal("A test issue", renamed.Title);
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal("a test issue", timelineEventInfos[0].Rename.From);
+ Assert.Equal("A test issue", timelineEventInfos[0].Rename.To);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeCrossReferenceEvent()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ newIssue = new NewIssue("another test issue") { Body = "Another new unassigned issue referencing the first new issue in #" + issue.Number };
+ observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var anotherNewIssue = await observable;
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.RepositoryOwner, _context.RepositoryName, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(anotherNewIssue.Id, timelineEventInfos[0].Source.Id);
+ }
+
+ [IntegrationTest]
+ public async Task CanRetrieveTimelineForIssueByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.Repository.Id, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Empty(timelineEventInfos);
+
+ observable = _client.Issue.Update(_context.Repository.Id, issue.Number, new IssueUpdate { State = ItemState.Closed });
+ var closed = await observable;
+ Assert.NotNull(closed);
+
+ observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.Repository.Id, issue.Number);
+ timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(EventInfoState.Closed, timelineEventInfos[0].Event);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeRenameEventByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ observable = _client.Issue.Update(_context.Repository.Id, issue.Number, new IssueUpdate { Title = "A test issue" });
+ var renamed = await observable;
+ Assert.NotNull(renamed);
+ Assert.Equal("A test issue", renamed.Title);
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.Repository.Id, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal("a test issue", timelineEventInfos[0].Rename.From);
+ Assert.Equal("A test issue", timelineEventInfos[0].Rename.To);
+ }
+
+ [IntegrationTest]
+ public async Task CanDeserializeCrossReferenceEventByRepositoryId()
+ {
+ var newIssue = new NewIssue("a test issue") { Body = "A new unassigned issue" };
+ var observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var issue = await observable;
+
+ newIssue = new NewIssue("another test issue") { Body = "Another new unassigned issue referencing the first new issue in #" + issue.Number };
+ observable = _client.Issue.Create(_context.Repository.Id, newIssue);
+ var anotherNewIssue = await observable;
+
+ var observableTimeline = _client.Issue.Timeline.GetAllForIssue(_context.Repository.Id, issue.Number);
+ var timelineEventInfos = await observableTimeline.ToList();
+ Assert.Equal(1, timelineEventInfos.Count);
+ Assert.Equal(anotherNewIssue.Id, timelineEventInfos[0].Source.Id);
+ }
+ }
+}
diff --git a/Octokit.Tests/Clients/IssueTimelineClientTests.cs b/Octokit.Tests/Clients/IssueTimelineClientTests.cs
new file mode 100644
index 0000000000..694cedd7f2
--- /dev/null
+++ b/Octokit.Tests/Clients/IssueTimelineClientTests.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using NSubstitute;
+using Xunit;
+
+namespace Octokit.Tests.Clients
+{
+ public class IssueTimelineClientTests
+ {
+ public class TheCtor
+ {
+ [Fact]
+ public void EnsuresNonNullArguments()
+ {
+ Assert.Throws(
+ () => new IssueTimelineClient(null));
+ }
+ }
+
+ public class TheGetAllForIssueMethod
+ {
+ [Fact]
+ public async Task RequestsTheCorrectUrl()
+ {
+ var connection = Substitute.For();
+ var client = new IssueTimelineClient(connection);
+
+ await client.GetAllForIssue("fake", "repo", 42);
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview",
+ Arg.Any());
+ }
+
+ [Fact]
+ public async Task RequestsTheCorrectUrlWithApiOptions()
+ {
+ var connection = Substitute.For();
+ var client = new IssueTimelineClient(connection);
+
+ await client.GetAllForIssue("fake", "repo", 42, new ApiOptions {PageSize = 30});
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview",
+ Arg.Is(ao => ao.PageSize == 30));
+ }
+
+ [Fact]
+ public async Task RequestsTheCorrectUrlWithRepositoryId()
+ {
+ var connection = Substitute.For();
+ var client = new IssueTimelineClient(connection);
+
+ await client.GetAllForIssue(1, 42);
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "repositories/1/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview",
+ Arg.Any());
+ }
+
+ [Fact]
+ public async Task RequestsTheCorrectUrlWithRepositoryIdAndApiOptions()
+ {
+ var connection = Substitute.For();
+ var client = new IssueTimelineClient(connection);
+
+ await client.GetAllForIssue(1, 42, new ApiOptions {PageSize = 30});
+
+ connection.Received().GetAll(
+ Arg.Is(u => u.ToString() == "repositories/1/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview",
+ Arg.Is(ao => ao.PageSize == 30));
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new IssueTimelineClient(Substitute.For());
+
+ await Assert.ThrowsAsync(() => client.GetAllForIssue(null, "repo", 42));
+ await Assert.ThrowsAsync(() => client.GetAllForIssue("owner", null, 42));
+ await Assert.ThrowsAsync(() => client.GetAllForIssue("owner", "repo", 42, null));
+ await Assert.ThrowsAsync(() => client.GetAllForIssue(1, 42, null));
+
+ await Assert.ThrowsAsync(() => client.GetAllForIssue("", "repo", 42));
+ await Assert.ThrowsAsync(() => client.GetAllForIssue("owner", "", 42));
+
+ }
+ }
+ }
+}
diff --git a/Octokit.Tests/Octokit.Tests.csproj b/Octokit.Tests/Octokit.Tests.csproj
index a05b3f8092..673961abfb 100644
--- a/Octokit.Tests/Octokit.Tests.csproj
+++ b/Octokit.Tests/Octokit.Tests.csproj
@@ -88,6 +88,7 @@
+
@@ -208,6 +209,7 @@
+
diff --git a/Octokit.Tests/Reactive/ObservableIssueTimelineClientTests.cs b/Octokit.Tests/Reactive/ObservableIssueTimelineClientTests.cs
new file mode 100644
index 0000000000..776f84352b
--- /dev/null
+++ b/Octokit.Tests/Reactive/ObservableIssueTimelineClientTests.cs
@@ -0,0 +1,144 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using NSubstitute;
+using Octokit.Internal;
+using Octokit.Reactive;
+using Xunit;
+
+namespace Octokit.Tests.Reactive
+{
+ public class ObservableIssueTimelineClientTests
+ {
+ public class TheCtor
+ {
+ [Fact]
+ public void EnsuresNonNullArguments()
+ {
+ Assert.Throws(
+ () => new ObservableIssueTimelineClient(null));
+ }
+ }
+
+ public class TheGetAllForIssueMethod
+ {
+ [Fact]
+ public async Task RequestsCorrectUrl()
+ {
+ var result = new List { new TimelineEventInfo() };
+
+ var connection = Substitute.For();
+ var gitHubClient = new GitHubClient(connection);
+ var client = new ObservableIssueTimelineClient(gitHubClient);
+
+ IApiResponse> response = new ApiResponse>(
+ new Response
+ {
+ ApiInfo = new ApiInfo(new Dictionary(), new List(), new List(), "etag", new RateLimit()),
+ }, result);
+ gitHubClient.Connection.Get>(Args.Uri, Args.EmptyDictionary, "application/vnd.github.mockingbird-preview")
+ .Returns(Task.FromResult(response));
+
+ var timelineEvents = await client.GetAllForIssue("fake", "repo", 42).ToList();
+
+ connection.Received().Get>(
+ Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview");
+ Assert.Equal(1, timelineEvents.Count);
+ }
+
+ [Fact]
+ public async Task RequestsCorrectUrlWithApiOptions()
+ {
+ var result = new List { new TimelineEventInfo() };
+
+ var connection = Substitute.For();
+ var gitHubClient = new GitHubClient(connection);
+ var client = new ObservableIssueTimelineClient(gitHubClient);
+
+ IApiResponse> response = new ApiResponse>(
+ new Response
+ {
+ ApiInfo = new ApiInfo(new Dictionary(), new List(), new List(), "etag", new RateLimit()),
+ }, result);
+ gitHubClient.Connection.Get>(Args.Uri, Arg.Is>(d => d.Count == 1), "application/vnd.github.mockingbird-preview")
+ .Returns(Task.FromResult(response));
+
+ var timelineEvents = await client.GetAllForIssue("fake", "repo", 42, new ApiOptions {PageSize = 30}).ToList();
+
+ connection.Received().Get>(
+ Arg.Is(u => u.ToString() == "repos/fake/repo/issues/42/timeline"),
+ Arg.Is>(d => d.Count == 1 && d["per_page"] == "30"),
+ "application/vnd.github.mockingbird-preview");
+ Assert.Equal(1, timelineEvents.Count);
+ }
+
+ [Fact]
+ public async Task RequestCorrectUrlWithRepositoryId()
+ {
+ var result = new List { new TimelineEventInfo() };
+ var connection = Substitute.For();
+ var githubClient = new GitHubClient(connection);
+ var client = new ObservableIssueTimelineClient(githubClient);
+
+ IApiResponse> response = new ApiResponse>(
+ new Response
+ {
+ ApiInfo = new ApiInfo(new Dictionary(), new List(), new List(), "etag", new RateLimit()),
+ }, result);
+ githubClient.Connection.Get>(Args.Uri, Args.EmptyDictionary, "application/vnd.github.mockingbird-preview")
+ .Returns(Task.FromResult(response));
+
+ var timelineEvents = await client.GetAllForIssue(1, 42).ToList();
+
+ connection.Received().Get>(
+ Arg.Is(u => u.ToString() == "repositories/1/issues/42/timeline"),
+ Arg.Any>(),
+ "application/vnd.github.mockingbird-preview");
+ Assert.Equal(1, timelineEvents.Count);
+ }
+
+ [Fact]
+ public async Task RequestCorrectUrlWithRepositoryIdAndApiOptions()
+ {
+ var result = new List { new TimelineEventInfo() };
+ var connection = Substitute.For();
+ var githubClient = new GitHubClient(connection);
+ var client = new ObservableIssueTimelineClient(githubClient);
+
+ IApiResponse> response = new ApiResponse>(
+ new Response
+ {
+ ApiInfo = new ApiInfo(new Dictionary(), new List(), new List(), "etag", new RateLimit()),
+ }, result);
+ githubClient.Connection.Get>(Args.Uri, Arg.Is>(d => d.Count == 1), "application/vnd.github.mockingbird-preview")
+ .Returns(Task.FromResult(response));
+
+ var timelineEvents = await client.GetAllForIssue(1, 42, new ApiOptions {PageSize = 30}).ToList();
+
+ connection.Received().Get>(
+ Arg.Is(u => u.ToString() == "repositories/1/issues/42/timeline"),
+ Arg.Is>(d => d.Count == 1 && d["per_page"] == "30"),
+ "application/vnd.github.mockingbird-preview");
+ Assert.Equal(1, timelineEvents.Count);
+ }
+
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var client = new ObservableIssueTimelineClient(Substitute.For());
+
+ Assert.Throws(() => client.GetAllForIssue(null, "repo", 42));
+ Assert.Throws(() => client.GetAllForIssue("owner", null, 42));
+ Assert.Throws(() => client.GetAllForIssue("owner", "repo", 42, null));
+ Assert.Throws(() => client.GetAllForIssue(1, 42, null));
+
+ Assert.Throws(() => client.GetAllForIssue("", "repo", 42));
+ Assert.Throws(() => client.GetAllForIssue("owner", "", 42));
+
+ }
+ }
+ }
+}
diff --git a/Octokit/Clients/IIssueTimelineClient.cs b/Octokit/Clients/IIssueTimelineClient.cs
new file mode 100644
index 0000000000..e6321ea638
--- /dev/null
+++ b/Octokit/Clients/IIssueTimelineClient.cs
@@ -0,0 +1,60 @@
+#if NET_45
+using System.Collections.Generic;
+using System.Threading.Tasks;
+#endif
+
+namespace Octokit
+{
+ ///
+ /// A client for GitHub's Issue Timeline API.
+ ///
+ ///
+ /// See the Issue Timeline API documentation for more information.
+ ///
+ public interface IIssueTimelineClient
+ {
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ Task> GetAllForIssue(string owner, string repo, int number);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ /// Options for changing the API repsonse
+ Task> GetAllForIssue(string owner, string repo, int number, ApiOptions options);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ Task> GetAllForIssue(int repositoryId, int number);
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ /// Options for changing the API response
+ Task> GetAllForIssue(int repositoryId, int number, ApiOptions options);
+ }
+}
diff --git a/Octokit/Clients/IIssuesClient.cs b/Octokit/Clients/IIssuesClient.cs
index 5dbfb1d948..81f8843b09 100644
--- a/Octokit/Clients/IIssuesClient.cs
+++ b/Octokit/Clients/IIssuesClient.cs
@@ -39,6 +39,8 @@ public interface IIssuesClient
///
IIssueCommentsClient Comment { get; }
+ IIssueTimelineClient Timeline { get; }
+
///
/// Gets a single Issue by number.
///
diff --git a/Octokit/Clients/IssueTimelineClient.cs b/Octokit/Clients/IssueTimelineClient.cs
new file mode 100644
index 0000000000..ec71d3f3d0
--- /dev/null
+++ b/Octokit/Clients/IssueTimelineClient.cs
@@ -0,0 +1,85 @@
+#if NET_45
+using System.Collections.Generic;
+using System.Threading.Tasks;
+#endif
+
+namespace Octokit
+{
+ ///
+ /// A client for GitHub's Issue Timeline API.
+ ///
+ ///
+ /// See the Issue Timeline API documentation for more information.
+ ///
+ public class IssueTimelineClient: ApiClient, IIssueTimelineClient
+ {
+ public IssueTimelineClient(IApiConnection apiConnection) : base(apiConnection)
+ {
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ public Task> GetAllForIssue(string owner, string repo, int number)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
+ Ensure.ArgumentNotNullOrEmptyString(repo, "repo");
+
+ return GetAllForIssue(owner, repo, number, ApiOptions.None);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ /// Options for changing the API repsonse
+ public Task> GetAllForIssue(string owner, string repo, int number, ApiOptions options)
+ {
+ Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
+ Ensure.ArgumentNotNullOrEmptyString(repo, "repo");
+ Ensure.ArgumentNotNull(options, "options");
+
+ return ApiConnection.GetAll(ApiUrls.IssueTimeline(owner, repo, number), null, AcceptHeaders.IssueTimelineApiPreview, options);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ public Task> GetAllForIssue(int repositoryId, int number)
+ {
+ return GetAllForIssue(repositoryId, number, ApiOptions.None);
+ }
+
+ ///
+ /// Gets all the various events that have occurred around an issue or pull request.
+ ///
+ ///
+ /// https://developer.github.com/v3/issues/timeline/#list-events-for-an-issue
+ ///
+ /// The Id of the repository
+ /// The issue number
+ /// Options for changing the API response
+ public Task> GetAllForIssue(int repositoryId, int number, ApiOptions options)
+ {
+ Ensure.ArgumentNotNull(options, "options");
+
+ return ApiConnection.GetAll(ApiUrls.IssueTimeline(repositoryId, number), null, AcceptHeaders.IssueTimelineApiPreview, options);
+ }
+ }
+}
diff --git a/Octokit/Clients/IssuesClient.cs b/Octokit/Clients/IssuesClient.cs
index 7387527892..3542da4e4b 100644
--- a/Octokit/Clients/IssuesClient.cs
+++ b/Octokit/Clients/IssuesClient.cs
@@ -22,6 +22,7 @@ public IssuesClient(IApiConnection apiConnection) : base(apiConnection)
Labels = new IssuesLabelsClient(apiConnection);
Milestone = new MilestonesClient(apiConnection);
Comment = new IssueCommentsClient(apiConnection);
+ Timeline = new IssueTimelineClient(apiConnection);
}
///
@@ -51,6 +52,11 @@ public IssuesClient(IApiConnection apiConnection) : base(apiConnection)
///
public IIssueCommentsClient Comment { get; private set; }
+ ///
+ /// Client for reading the timeline of events for an issue
+ ///
+ public IIssueTimelineClient Timeline { get; private set; }
+
///
/// Gets a single Issue by number.
///
diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs
index 6082aa3f21..f85ebc322f 100644
--- a/Octokit/Helpers/AcceptHeaders.cs
+++ b/Octokit/Helpers/AcceptHeaders.cs
@@ -38,5 +38,7 @@ public static class AcceptHeaders
public const string InvitationsApiPreview = "application/vnd.github.swamp-thing-preview+json";
public const string PagesApiPreview = "application/vnd.github.mister-fantastic-preview+json";
+
+ public const string IssueTimelineApiPreview = "application/vnd.github.mockingbird-preview";
}
}
diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs
index 5dbfe3d313..c4d964e849 100644
--- a/Octokit/Helpers/ApiUrls.cs
+++ b/Octokit/Helpers/ApiUrls.cs
@@ -341,6 +341,29 @@ public static Uri IssueReactions(int repositoryId, int number)
return "repositories/{0}/issues/{1}/reactions".FormatUri(repositoryId, number);
}
+ ///
+ /// Returns the for the timeline of a specified issue.
+ ///
+ /// The owner of the repository
+ /// The name of the repository
+ /// The issue number
+ ///
+ public static Uri IssueTimeline(string owner, string repo, int number)
+ {
+ return "repos/{0}/{1}/issues/{2}/timeline".FormatUri(owner, repo, number);
+ }
+
+ ///
+ /// Returns the for the timeline of a specified issue.
+ ///
+ /// The Id of the repository
+ /// The issue number
+ ///
+ public static Uri IssueTimeline(int repositoryId, int number)
+ {
+ return "repositories/{0}/issues/{1}/timeline".FormatUri(repositoryId, number);
+ }
+
///
/// Returns the for the comments for all issues in a specific repo.
///
diff --git a/Octokit/Models/Response/EventInfo.cs b/Octokit/Models/Response/EventInfo.cs
index a659994104..cd4529570e 100644
--- a/Octokit/Models/Response/EventInfo.cs
+++ b/Octokit/Models/Response/EventInfo.cs
@@ -165,6 +165,25 @@ public enum EventInfoState
///
/// The actor unsubscribed from notifications for an issue.
///
- Unsubscribed
+ Unsubscribed,
+
+ ///
+ /// A comment was added to the issue.
+ ///
+ Commented,
+
+ ///
+ /// A commit was added to the pull request's HEAD branch.
+ /// Only provided for pull requests.
+ ///
+ Committed,
+
+ ///
+ /// The issue was referenced from another issue.
+ /// The source attribute contains the id, actor, and
+ /// url of the reference's source.
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crossreferenced")]
+ Crossreferenced
}
}
\ No newline at end of file
diff --git a/Octokit/Models/Response/RenameInfo.cs b/Octokit/Models/Response/RenameInfo.cs
new file mode 100644
index 0000000000..d21f0f3234
--- /dev/null
+++ b/Octokit/Models/Response/RenameInfo.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class RenameInfo
+ {
+ public string From { get; protected set; }
+ public string To { get; protected set; }
+
+ internal string DebuggerDisplay
+ {
+ get { return string.Format(CultureInfo.InvariantCulture, "From: {0} To: {1}", From, To); }
+ }
+ }
+}
diff --git a/Octokit/Models/Response/SourceInfo.cs b/Octokit/Models/Response/SourceInfo.cs
new file mode 100644
index 0000000000..ff91bdfec5
--- /dev/null
+++ b/Octokit/Models/Response/SourceInfo.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class SourceInfo
+ {
+ public User Actor { get; protected set; }
+ public int Id { get; protected set; }
+ public string Url { get; protected set; }
+
+ internal string DebuggerDisplay
+ {
+ get { return string.Format(CultureInfo.InvariantCulture, "Id: {0} Url: {1}", Id, Url); }
+ }
+ }
+}
diff --git a/Octokit/Models/Response/TimelineEventInfo.cs b/Octokit/Models/Response/TimelineEventInfo.cs
new file mode 100644
index 0000000000..f67c6fc7dd
--- /dev/null
+++ b/Octokit/Models/Response/TimelineEventInfo.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Octokit
+{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
+ public class TimelineEventInfo
+ {
+ public TimelineEventInfo() { }
+
+ public TimelineEventInfo(int id, string url, User actor, string commitId, EventInfoState @event, DateTimeOffset createdAt, Label label, User assignee, Milestone milestone, SourceInfo source, RenameInfo rename)
+ {
+ Id = id;
+ Url = url;
+ Actor = actor;
+ CommitId = commitId;
+ Event = @event;
+ CreatedAt = createdAt;
+ Label = label;
+ Assignee = assignee;
+ Milestone = milestone;
+ Source = source;
+ Rename = rename;
+ }
+
+ public int Id { get; protected set; }
+ public string Url { get; protected set; }
+ public User Actor { get; protected set; }
+ public string CommitId { get; protected set; }
+ public EventInfoState Event { get; protected set; }
+ public DateTimeOffset CreatedAt { get; protected set; }
+ public Label Label { get; protected set; }
+ public User Assignee { get; protected set; }
+ public Milestone Milestone { get; protected set; }
+ public SourceInfo Source { get; protected set; }
+ public RenameInfo Rename { get; protected set; }
+
+ internal string DebuggerDisplay
+ {
+ get { return string.Format(CultureInfo.InvariantCulture, "Id: {0} CreatedAt: {1} Event: {2}", Id, CreatedAt, Event); }
+ }
+ }
+}
diff --git a/Octokit/Octokit-Mono.csproj b/Octokit/Octokit-Mono.csproj
index dda54a6619..6671612640 100644
--- a/Octokit/Octokit-Mono.csproj
+++ b/Octokit/Octokit-Mono.csproj
@@ -484,6 +484,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Octokit/Octokit-MonoAndroid.csproj b/Octokit/Octokit-MonoAndroid.csproj
index 2c472d67d9..3b494a38e5 100644
--- a/Octokit/Octokit-MonoAndroid.csproj
+++ b/Octokit/Octokit-MonoAndroid.csproj
@@ -495,6 +495,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Octokit/Octokit-Monotouch.csproj b/Octokit/Octokit-Monotouch.csproj
index 16c0e17c21..40f8ba1911 100644
--- a/Octokit/Octokit-Monotouch.csproj
+++ b/Octokit/Octokit-Monotouch.csproj
@@ -491,6 +491,11 @@
+
+
+
+
+
diff --git a/Octokit/Octokit-Portable.csproj b/Octokit/Octokit-Portable.csproj
index f28e8a25ac..3270a8efa6 100644
--- a/Octokit/Octokit-Portable.csproj
+++ b/Octokit/Octokit-Portable.csproj
@@ -481,6 +481,11 @@
+
+
+
+
+
diff --git a/Octokit/Octokit-netcore45.csproj b/Octokit/Octokit-netcore45.csproj
index d807f7c109..c0b3f04f29 100644
--- a/Octokit/Octokit-netcore45.csproj
+++ b/Octokit/Octokit-netcore45.csproj
@@ -488,6 +488,11 @@
+
+
+
+
+
diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj
index 4adbffc62a..0e0f5347c4 100644
--- a/Octokit/Octokit.csproj
+++ b/Octokit/Octokit.csproj
@@ -61,10 +61,12 @@
+
+
@@ -207,7 +209,10 @@
+
+
+