Merge pull request #428 from octokit/shiftkey/compare-two-commits

Add support for comparing two commits
This commit is contained in:
Phil Haack
2014-03-23 09:29:10 -07:00
26 changed files with 450 additions and 6 deletions
@@ -221,6 +221,20 @@ namespace Octokit.Reactive
/// </remarks>
IObservableRepoCollaboratorsClient RepoCollaborators { get; }
/// <summary>
/// Client for GitHub's Repository Commits API
/// </summary>
/// <remarks>
/// See the <a href="http://developer.github.com/v3/repos/commits/">Commits API documentation</a> for more details
///</remarks>
IObservableRepositoryCommitsClient Commits { get; }
/// <summary>
/// Client for managing pull requests.
/// </summary>
/// <remarks>
/// See the <a href="http://developer.github.com/v3/pulls/">Pull Requests API documentation</a> for more details
/// </remarks>
IObservablePullRequestsClient PullRequest { get; }
}
}
@@ -0,0 +1,19 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Octokit.Reactive
{
public interface IObservableRepositoryCommitsClient
{
/// <summary>
/// Compare two references in a repository
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The reference to use as the base commit</param>
/// <param name="head">The reference to use as the head commit</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "base")]
IObservable<CompareResult> Compare(string owner, string name, string @base, string @head);
}
}
@@ -25,6 +25,7 @@ namespace Octokit.Reactive
Statistics = new ObservableStatisticsClient(client);
PullRequest = new ObservablePullRequestsClient(client);
RepositoryComments = new ObservableRepositoryCommentsClient(client);
Commits = new ObservableRepositoryCommitsClient(client);
}
/// <summary>
@@ -328,6 +329,19 @@ namespace Octokit.Reactive
return _client.Edit(owner, name, update).ToObservable();
}
/// <summary>
/// Compare two references in a repository
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The reference to use as the base commit</param>
/// <param name="head">The reference to use as the head commit</param>
/// <returns></returns>
public IObservable<CompareResult> Compare(string owner, string name, string @base, string head)
{
return _client.Commits.Compare(owner, name, @base, head).ToObservable();
}
/// <summary>
/// A client for GitHub's Repo Collaborators.
/// </summary>
@@ -336,6 +350,14 @@ namespace Octokit.Reactive
/// </remarks>
public IObservableRepoCollaboratorsClient RepoCollaborators { get; private set; }
/// <summary>
/// Client for GitHub's Repository Commits API
/// </summary>
/// <remarks>
/// See the <a href="http://developer.github.com/v3/repos/commits/">Commits API documentation</a> for more details
///</remarks>
public IObservableRepositoryCommitsClient Commits { get; private set; }
/// <summary>
/// Client for managing pull requests.
/// </summary>
@@ -0,0 +1,28 @@
using System;
using System.Reactive.Threading.Tasks;
namespace Octokit.Reactive
{
public class ObservableRepositoryCommitsClient : IObservableRepositoryCommitsClient
{
readonly IGitHubClient _client;
public ObservableRepositoryCommitsClient(IGitHubClient client)
{
_client = client;
}
/// <summary>
/// Compare two references in a repository
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The reference to use as the base commit</param>
/// <param name="head">The reference to use as the head commit</param>
/// <returns></returns>
public IObservable<CompareResult> Compare(string owner, string name, string @base, string head)
{
return _client.Repository.Commits.Compare(owner, name, @base, head).ToObservable();
}
}
}
@@ -141,6 +141,8 @@
<Compile Include="Clients\ObservableIssuesLabelsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@@ -150,6 +150,8 @@
<Compile Include="Clients\ObservableIssuesLabelsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
<ItemGroup>
@@ -145,6 +145,8 @@
<Compile Include="Clients\ObservableIssuesLabelsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
+2
View File
@@ -73,6 +73,7 @@
<Compile Include="..\SolutionInfo.cs">
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Clients\IObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableRepositoryCommentsClient.cs" />
<Compile Include="Clients\IObservableDeploymentsClient.cs" />
@@ -87,6 +88,7 @@
<Compile Include="Clients\IObservableStatisticsClient.cs" />
<Compile Include="Clients\ObservableFeedsClient.cs" />
<Compile Include="Clients\ObservableIssuesLabelsClient.cs" />
<Compile Include="Clients\ObservableRepositoryCommitsClients.cs" />
<Compile Include="Clients\ObservableSearchClient.cs" />
<Compile Include="Clients\IObservableBlobsClient.cs" />
<Compile Include="Clients\IObservableGistCommentsClient.cs" />
@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;
using Octokit;
using Octokit.Tests.Integration;
using Xunit;
public class RepositoryCommitsClientTests : IDisposable
{
readonly IGitHubClient _client;
IRepositoryCommitsClient _fixture;
Repository _repository;
public RepositoryCommitsClientTests()
{
_client = new GitHubClient(new ProductHeaderValue("OctokitTests"))
{
Credentials = Helper.Credentials
};
_fixture = _client.Repository.Commits;
var repoName = Helper.MakeNameWithTimestamp("source-repo");
_repository = _client.Repository.Create(new NewRepository { Name = repoName, AutoInit = true }).Result;
}
[IntegrationTest]
public async Task CanCompareReferences()
{
await CreateTheWorld();
var result = await _fixture.Compare(Helper.UserName, _repository.Name, "master", "my-branch");
Assert.Equal(1, result.TotalCommits);
Assert.Equal(1, result.Commits.Count);
Assert.Equal(1, result.AheadBy);
Assert.Equal(0, result.BehindBy);
}
[IntegrationTest]
public async Task CanCompareReferencesOtherWayRound()
{
await CreateTheWorld();
var result = await _fixture.Compare(Helper.UserName, _repository.Name, "my-branch", "master");
Assert.Equal(0, result.TotalCommits);
Assert.Equal(0, result.Commits.Count);
Assert.Equal(0, result.AheadBy);
Assert.Equal(1, result.BehindBy);
}
[IntegrationTest]
public async Task ReturnsUrlsToResources()
{
await CreateTheWorld();
var result = await _fixture.Compare(Helper.UserName, _repository.Name, "my-branch", "master");
Assert.NotNull(result.DiffUrl);
Assert.NotNull(result.HtmlUrl);
Assert.NotNull(result.PatchUrl);
Assert.NotNull(result.PermalinkUrl);
}
[IntegrationTest]
public async Task CanCompareUsingSha()
{
await CreateTheWorld();
var master = await _client.GitDatabase.Reference.Get(Helper.UserName, _repository.Name, "heads/master");
var branch = await _client.GitDatabase.Reference.Get(Helper.UserName, _repository.Name, "heads/my-branch");
var result = await _fixture.Compare(Helper.UserName, _repository.Name, master.Object.Sha, branch.Object.Sha);
Assert.Equal(1, result.Commits.Count);
Assert.Equal(1, result.AheadBy);
Assert.Equal(0, result.BehindBy);
}
async Task CreateTheWorld()
{
var master = await _client.GitDatabase.Reference.Get(Helper.UserName, _repository.Name, "heads/master");
// create new commit for master branch
var newMasterTree = await CreateTree(new Dictionary<string, string> { { "README.md", "Hello World!" } });
var newMaster = await CreateCommit("baseline for pull request", newMasterTree.Sha, master.Object.Sha);
// update master
await _client.GitDatabase.Reference.Update(Helper.UserName, _repository.Name, "heads/master", new ReferenceUpdate(newMaster.Sha));
// create new commit for feature branch
var featureBranchTree = await CreateTree(new Dictionary<string, string> { { "README.md", "I am overwriting this blob with something new" } });
var newFeature = await CreateCommit("this is the commit to merge into the pull request", featureBranchTree.Sha, newMaster.Sha);
// create branch
await _client.GitDatabase.Reference.Create(Helper.UserName, _repository.Name, new NewReference("refs/heads/my-branch", newFeature.Sha));
}
async Task<TreeResponse> CreateTree(IDictionary<string, string> treeContents)
{
var collection = new List<NewTreeItem>();
foreach (var c in treeContents)
{
var baselineBlob = new NewBlob
{
Content = c.Value,
Encoding = EncodingType.Utf8
};
var baselineBlobResult = await _client.GitDatabase.Blob.Create(Helper.UserName, _repository.Name, baselineBlob);
collection.Add(new NewTreeItem
{
Type = TreeType.Blob,
Mode = FileMode.File,
Path = c.Key,
Sha = baselineBlobResult.Sha
});
}
var newTree = new NewTree { Tree = collection };
return await _client.GitDatabase.Tree.Create(Helper.UserName, _repository.Name, newTree);
}
async Task<Commit> CreateCommit(string message, string sha, string parent)
{
var newCommit = new NewCommit(message, sha, parent);
return await _client.GitDatabase.Commit.Create(Helper.UserName, _repository.Name, newCommit);
}
public void Dispose()
{
_client.Repository.Delete(_repository.Owner.Login, _repository.Name);
}
}
@@ -71,6 +71,7 @@
<Compile Include="Clients\MilestonesClientTests.cs" />
<Compile Include="Clients\PullRequestsClientTests.cs" />
<Compile Include="Clients\ReferencesClientTests.cs" />
<Compile Include="Clients\RepositoryCommitsClientTests.cs" />
<Compile Include="Clients\SearchClientTests.cs" />
<Compile Include="Clients\StatisticsClientTests.cs" />
<Compile Include="Clients\TreeClientTests.cs" />
@@ -35,7 +35,7 @@ namespace Octokit.Tests.Clients
await AssertEx.Throws<ArgumentNullException>(async () => await client.Create(null));
await AssertEx.Throws<ArgumentException>(async () => await client.Create(new NewRepository { Name = null }));
}
[Fact]
public void UsesTheUserReposUrl()
{
@@ -345,9 +345,9 @@ namespace Octokit.Tests.Clients
var readme = await reposEndpoint.GetReadme("fake", "repo");
Assert.Equal("README.md", readme.Name);
connection.Received().Get<ReadmeResponse>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/readme"),
connection.Received().Get<ReadmeResponse>(Arg.Is<Uri>(u => u.ToString() == "repos/fake/repo/readme"),
null);
connection.DidNotReceive().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"),
connection.DidNotReceive().GetHtml(Arg.Is<Uri>(u => u.ToString() == "https://github.example.com/readme"),
null);
var htmlReadme = await readme.GetHtmlContent();
Assert.Equal("<html>README</html>", htmlReadme);
@@ -557,5 +557,52 @@ namespace Octokit.Tests.Clients
Assert.Throws<ArgumentException>(() => client.Edit("owner", "", update));
}
}
public class TheCompareMethod
{
[Fact]
public void EnsureNonNullArguments()
{
var client = new RepositoryCommitsClient(Substitute.For<IApiConnection>());
Assert.Throws<ArgumentNullException>(() => client.Compare(null, "repo", "base", "head"));
Assert.Throws<ArgumentException>(() => client.Compare("", "repo", "base", "head"));
Assert.Throws<ArgumentNullException>(() => client.Compare("owner", null, "base", "head"));
Assert.Throws<ArgumentException>(() => client.Compare("owner", "", "base", "head"));
Assert.Throws<ArgumentNullException>(() => client.Compare("owner", "repo", null, "head"));
Assert.Throws<ArgumentException>(() => client.Compare("owner", "repo", "", "head"));
Assert.Throws<ArgumentNullException>(() => client.Compare("owner", "repo", "base", null));
Assert.Throws<ArgumentException>(() => client.Compare("owner", "repo", "base", ""));
}
[Fact]
public void GetsCorrectUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoryCommitsClient(connection);
client.Compare("owner", "repo", "base", "head");
connection.Received()
.Get<CompareResult>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo/compare/base...head"), null);
}
[Fact]
public void EncodesUrl()
{
var connection = Substitute.For<IApiConnection>();
var client = new RepositoryCommitsClient(connection);
client.Compare("owner", "repo", "base", "shiftkey/my-cool-branch");
connection.Received()
.Get<CompareResult>(Arg.Is<Uri>(u => u.ToString() == "repos/owner/repo/compare/base...shiftkey%2Fmy-cool-branch"), null);
}
}
}
}
+8
View File
@@ -176,6 +176,14 @@ namespace Octokit
///</remarks>
IStatisticsClient Statistics { get; }
/// <summary>
/// Client for GitHub's Repository Commits API
/// </summary>
/// <remarks>
/// See the <a href="http://developer.github.com/v3/repos/commits/">Commits API documentation</a> for more details
///</remarks>
IRepositoryCommitsClient Commits { get; }
/// <summary>
/// Gets all the branches for the specified repository.
/// </summary>
@@ -0,0 +1,19 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Octokit
{
public interface IRepositoryCommitsClient
{
/// <summary>
/// Compare two references in a repository
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The reference to use as the base commit</param>
/// <param name="head">The reference to use as the head commit</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "base")]
Task<CompareResult> Compare(string owner, string name, string @base, string head);
}
}
+9
View File
@@ -28,6 +28,7 @@ namespace Octokit
Deployment = new DeploymentsClient(apiConnection);
PullRequest = new PullRequestsClient(apiConnection);
RepositoryComments = new RepositoryCommentsClient(apiConnection);
Commits = new RepositoryCommitsClient(apiConnection);
}
/// <summary>
@@ -279,6 +280,14 @@ namespace Octokit
///</remarks>
public IStatisticsClient Statistics { get; private set; }
/// <summary>
/// Client for GitHub's Repository Commits API
/// </summary>
/// <remarks>
/// See the <a href="http://developer.github.com/v3/repos/commits/">Commits API documentation</a> for more details
///</remarks>
public IRepositoryCommitsClient Commits { get; private set; }
/// <summary>
/// Client for managing pull requests.
/// </summary>
@@ -0,0 +1,32 @@
using System.Threading.Tasks;
namespace Octokit
{
public class RepositoryCommitsClient : IRepositoryCommitsClient
{
readonly IApiConnection _apiConnection;
public RepositoryCommitsClient(IApiConnection apiConnection)
{
_apiConnection = apiConnection;
}
/// <summary>
/// Compare two references in a repository
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The reference to use as the base commit</param>
/// <param name="head">The reference to use as the head commit</param>
/// <returns></returns>
public Task<CompareResult> Compare(string owner, string name, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNullOrEmptyString(@base, "base");
Ensure.ArgumentNotNullOrEmptyString(head, "head");
return _apiConnection.Get<CompareResult>(ApiUrls.RepoCompare(owner, name, @base, head));
}
}
}
+20
View File
@@ -1049,6 +1049,26 @@ namespace Octokit
return "repos/{0}/{1}/tags".FormatUri(owner, name);
}
/// <summary>
/// Returns the <see cref="Uri"/> for comparing two commits.
/// </summary>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="base">The base commit</param>
/// <param name="head">The head commit</param>
/// <returns></returns>
public static Uri RepoCompare(string owner, string name, string @base, string head)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
Ensure.ArgumentNotNullOrEmptyString(name, "name");
Ensure.ArgumentNotNullOrEmptyString(@base, "base");
Ensure.ArgumentNotNullOrEmptyString(head, "head");
var encodedBase = @base.UriEncode();
var encodedHead = head.UriEncode();
return "repos/{0}/{1}/compare/{2}...{3}".FormatUri(owner, name, encodedBase, encodedHead);
}
/// <summary>
/// Returns the <see cref="Uri"/> for a repository branch.
/// </summary>
+5
View File
@@ -26,6 +26,11 @@ namespace Octokit
return new Uri(string.Format(CultureInfo.InvariantCulture, pattern, args), UriKind.Relative);
}
public static string UriEncode(this string input)
{
return System.Net.WebUtility.UrlEncode(input);
}
static readonly Regex _optionalQueryStringRegex = new Regex("\\{\\?([^}]+)\\}");
public static Uri ExpandUriTemplate(this string template, object values)
{
+1 -3
View File
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace Octokit
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class CompareResult
{
public string Url { get; set; }
public string HtmlUrl { get; set; }
public string PermalinkUrl { get; set; }
public string DiffUrl { get; set; }
public string PatchUrl { get; set; }
public GitHubCommit BaseCommit { get; set; }
public GitHubCommit MergedBaseCommit { get; set; }
public string Status { get; set; }
public int AheadBy { get; set; }
public int BehindBy { get; set; }
public int TotalCommits { get; set; }
public IReadOnlyCollection<GitHubCommit> Commits { get; set; }
internal string DebuggerDisplay
{
get
{
return String.Format(CultureInfo.InvariantCulture, "Status: {0} Ahead By: {1}, Behind By: {2}", Status, AheadBy, BehindBy);
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace Octokit
{
/// <summary>
/// An enhanced git commit containing links to additional resources
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class GitHubCommit : GitReference
{
public Author Author { get; set; }
public string CommentsUrl { get; set; }
public Commit Commit { get; set; }
public Author Committer { get; set; }
public string HtmlUrl { get; set; }
public IReadOnlyList<GitReference> Parents { get; set; }
}
}
+4
View File
@@ -310,6 +310,10 @@
<Compile Include="Models\Response\SearchUsersResult.cs" />
<Compile Include="Models\Response\SearchCodeResult.cs" />
<Compile Include="Models\Response\SearchIssuesResult.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+4
View File
@@ -321,6 +321,10 @@
<Compile Include="Models\Response\SearchUsersResult.cs" />
<Compile Include="Models\Response\SearchCodeResult.cs" />
<Compile Include="Models\Response\SearchIssuesResult.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
+4
View File
@@ -316,6 +316,10 @@
<Compile Include="Models\Response\SearchUsersResult.cs" />
<Compile Include="Models\Response\SearchCodeResult.cs" />
<Compile Include="Models\Response\SearchIssuesResult.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+4
View File
@@ -307,6 +307,10 @@
<Compile Include="Models\Response\SearchCodeResult.cs" />
<Compile Include="Models\Response\SearchIssuesResult.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\CustomDictionary.xml">
+4
View File
@@ -311,6 +311,10 @@
<Compile Include="Models\Response\SearchUsersResult.cs" />
<Compile Include="Models\Response\SearchCodeResult.cs" />
<Compile Include="Models\Response\SearchIssuesResult.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
</ItemGroup>
<ItemGroup>
+4
View File
@@ -54,16 +54,20 @@
<Link>Properties\SolutionInfo.cs</Link>
</Compile>
<Compile Include="Clients\ActivitiesClient.cs" />
<Compile Include="Clients\IRepositoryCommitsClient.cs" />
<Compile Include="Clients\RepositoryCommentsClient.cs" />
<Compile Include="Clients\IRepositoryCommentsClient.cs" />
<Compile Include="Clients\FeedsClient.cs" />
<Compile Include="Clients\IFeedsClient.cs" />
<Compile Include="Clients\RepositoryCommitsClient.cs" />
<Compile Include="Exceptions\PrivateRepositoryQuotaExceededException.cs" />
<Compile Include="Exceptions\RepositoryExistsException.cs" />
<Compile Include="Helpers\ApiErrorExtensions.cs" />
<Compile Include="Helpers\ConcurrentCache.cs" />
<Compile Include="Helpers\HttpClientExtensions.cs" />
<Compile Include="Http\ProductHeaderValue.cs" />
<Compile Include="Models\Response\GitHubCommit.cs" />
<Compile Include="Models\Response\CompareResult.cs" />
<Compile Include="Models\Request\NewCommitComment.cs" />
<Compile Include="Models\Response\CommitComment.cs" />
<Compile Include="Models\Response\DeploymentStatus.cs" />