From f2847376fbd775025ac9d92986d8addc44a191eb Mon Sep 17 00:00:00 2001 From: Cody Ray Freeman Hoeft Date: Sun, 7 Feb 2016 09:07:10 -0800 Subject: [PATCH 1/6] Move the AppConfig authentication logic to the Repo class, this enables it to be turned off for testing --- SemDiff.Core/GitHub.cs | 55 +++++------------------------ SemDiff.Core/GitHubConfiguration.cs | 45 +++++++++++++++++++++++ SemDiff.Core/Repo.cs | 20 +++++++++-- SemDiff.Core/SemDiff.Core.csproj | 1 + 4 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 SemDiff.Core/GitHubConfiguration.cs diff --git a/SemDiff.Core/GitHub.cs b/SemDiff.Core/GitHub.cs index a75eab9..9e3a90c 100644 --- a/SemDiff.Core/GitHub.cs +++ b/SemDiff.Core/GitHub.cs @@ -26,9 +26,6 @@ public class GitHub private static string APIDoesNotExistError = "Not Found"; - static GitHubConfiguration gitHubConfig = - new GitHubConfiguration((AuthenticationSection)ConfigurationManager.GetSection("SemDiff.Core/authentication")); - public GitHub(string repoOwner, string repoName) { this.RepoOwner = repoOwner; @@ -42,16 +39,16 @@ public GitHub(string repoOwner, string repoName) Client.DefaultRequestHeaders.UserAgent.ParseAdd(nameof(SemDiff)); Client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json"); - string authToken = gitHubConfig.AuthenicationToken; - string authUsername = gitHubConfig.Username; - if (!string.IsNullOrEmpty(authToken) || !string.IsNullOrEmpty(authUsername)) - { - Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{authUsername}:{authToken}"))); - } - RepoFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SemDiff), RepoOwner, RepoName); } + public GitHub(string repoOwner, string repoName, string authUsername, string authToken) : this(repoOwner, repoName) + { + AuthUsername = authUsername; + AuthToken = authToken; + Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{AuthUsername}:{AuthToken}"))); + } + public string AuthToken { get; set; } public string AuthUsername { get; set; } public string RepoName { get; set; } @@ -202,41 +199,5 @@ public class HeadBase public string Ref { get; set; } public string Sha { get; set; } } - - struct GitHubConfiguration - { - readonly string authenticationToken; - readonly string username; - - public GitHubConfiguration(AuthenticationSection section) - { - if (section == null) - { - this.authenticationToken = null; - this.username = null; - } - else - { - this.authenticationToken = section.Authentication.Token; - this.username = section.Authentication.Username; - } - } - - public string AuthenicationToken - { - get - { - return this.authenticationToken; - } - } - - public string Username - { - get - { - return this.username; - } - } - } } -} +} \ No newline at end of file diff --git a/SemDiff.Core/GitHubConfiguration.cs b/SemDiff.Core/GitHubConfiguration.cs new file mode 100644 index 0000000..e32180f --- /dev/null +++ b/SemDiff.Core/GitHubConfiguration.cs @@ -0,0 +1,45 @@ +using SemDiff.Core.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SemDiff.Core +{ + internal struct GitHubConfiguration + { + private readonly string authenticationToken; + private readonly string username; + + public GitHubConfiguration(AuthenticationSection section) + { + if (section == null) + { + this.authenticationToken = null; + this.username = null; + } + else + { + this.authenticationToken = section.Authentication.Token; + this.username = section.Authentication.Username; + } + } + + public string AuthenicationToken + { + get + { + return this.authenticationToken; + } + } + + public string Username + { + get + { + return this.username; + } + } + } +} \ No newline at end of file diff --git a/SemDiff.Core/Repo.cs b/SemDiff.Core/Repo.cs index 7d968de..9c9c7fe 100644 --- a/SemDiff.Core/Repo.cs +++ b/SemDiff.Core/Repo.cs @@ -1,6 +1,8 @@ -using System; +using SemDiff.Core.Configuration; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Configuration; using System.IO; using System.Text.RegularExpressions; @@ -12,6 +14,10 @@ namespace SemDiff.Core public class Repo { private static readonly ConcurrentDictionary _repoLookup = new ConcurrentDictionary(); + public static bool Authentication { get; set; } = true; + + private static GitHubConfiguration gitHubConfig = + new GitHubConfiguration((AuthenticationSection)ConfigurationManager.GetSection("SemDiff.Core/authentication")); /// /// Looks for the git repo above the current file in the directory higherarchy. Null will be returned if no repo was found. @@ -49,6 +55,7 @@ internal static Repo AddRepo(string directoryPath) public string Owner { get; private set; } public string Name { get; private set; } + public GitHub GitHubApi { get; private set; } internal static Repo RepoFromConfig(string gitconfigPath) { @@ -74,6 +81,16 @@ internal static void ClearLookup() internal Repo(string owner, string name) { + if (Authentication) + { + string authToken = gitHubConfig.AuthenicationToken; + string authUsername = gitHubConfig.Username; + GitHubApi = new GitHub(owner, name, authUsername, authToken); + } + else + { + GitHubApi = new GitHub(owner, name); + } Owner = owner; Name = name; } @@ -95,7 +112,6 @@ public void TriggerUpdate() public void Update() { - throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/SemDiff.Core/SemDiff.Core.csproj b/SemDiff.Core/SemDiff.Core.csproj index c279d48..6c4bf1c 100644 --- a/SemDiff.Core/SemDiff.Core.csproj +++ b/SemDiff.Core/SemDiff.Core.csproj @@ -135,6 +135,7 @@ + From 0995096d667f9c8730b2edcc1e63b046bdc1da12 Mon Sep 17 00:00:00 2001 From: Cody Ray Freeman Hoeft Date: Sun, 7 Feb 2016 09:33:28 -0800 Subject: [PATCH 2/6] Add some (minimal) error handling to GitHub.cs and a retry once logic --- SemDiff.Core/Extensions.cs | 15 +++++++++++++++ SemDiff.Core/GitHub.cs | 12 +++++++++--- SemDiff.Core/Repo.cs | 2 +- SemDiff.Test/GitHubAuthTests.cs | 28 ++++++++++++++++++---------- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/SemDiff.Core/Extensions.cs b/SemDiff.Core/Extensions.cs index f3352a1..eff9dd9 100644 --- a/SemDiff.Core/Extensions.cs +++ b/SemDiff.Core/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SemDiff.Core { @@ -44,5 +45,19 @@ public static Queue GetMergedChangeQueue(this IEnumerable left, IEnumer } return result; } + + public async static Task RetryOnce(this Func> tsk, TimeSpan wait) + { + try + { + return await tsk(); + } + catch (Exception ex) + { + Logger.Error(ex.Message); + await Task.Delay(wait); + return await tsk(); + } + } } } \ No newline at end of file diff --git a/SemDiff.Core/GitHub.cs b/SemDiff.Core/GitHub.cs index 9e3a90c..6efa01c 100644 --- a/SemDiff.Core/GitHub.cs +++ b/SemDiff.Core/GitHub.cs @@ -86,11 +86,17 @@ private async Task HttpGetAsync(string url) private async Task HttpGetAsync(string url) { - //TODO: Handle Errors Here vv - var response = await Client.GetAsync(url); + //Request, but retry once waiting 5 minutes + var response = await Extensions.RetryOnce(() => Client.GetAsync(url), TimeSpan.FromMinutes(5)); if (!response.IsSuccessStatusCode) { - //TODO: Implement Check + switch (response.StatusCode) + { + case HttpStatusCode.Unauthorized: + throw new UnauthorizedAccessException("Authentication Failure"); + default: + throw new NotImplementedException(); + } } return await response.Content.ReadAsStringAsync(); } diff --git a/SemDiff.Core/Repo.cs b/SemDiff.Core/Repo.cs index 9c9c7fe..d6cefd1 100644 --- a/SemDiff.Core/Repo.cs +++ b/SemDiff.Core/Repo.cs @@ -16,7 +16,7 @@ public class Repo private static readonly ConcurrentDictionary _repoLookup = new ConcurrentDictionary(); public static bool Authentication { get; set; } = true; - private static GitHubConfiguration gitHubConfig = + internal static GitHubConfiguration gitHubConfig = new GitHubConfiguration((AuthenticationSection)ConfigurationManager.GetSection("SemDiff.Core/authentication")); /// diff --git a/SemDiff.Test/GitHubAuthTests.cs b/SemDiff.Test/GitHubAuthTests.cs index 1ad623f..0cdb0ce 100644 --- a/SemDiff.Test/GitHubAuthTests.cs +++ b/SemDiff.Test/GitHubAuthTests.cs @@ -1,30 +1,38 @@ namespace SemDiff.Test { - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; using Core; - using System.Collections.Generic; using Core.Configuration; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Collections.Generic; using System.Configuration; + using System.Threading.Tasks; [TestClass] public class GitHubAuthTest { - string owner = "semdiffdotnet"; - string repository = "curly-broccoli"; + private string owner = "semdiffdotnet"; + private string repository = "curly-broccoli"; - IList pullRequests; - GitHub github; + private IList pullRequests; + private GitHub github; public GitHubAuthTest() { - this.github = new GitHub(this.owner, this.repository); + github = new GitHub(owner, repository, Repo.gitHubConfig.Username, Repo.gitHubConfig.AuthenicationToken); } [TestMethod] - public void AuthorizedPullRequests() + public async Task AuthorizedPullRequests() { - this.pullRequests = this.github.GetPullRequests().Result; + try + { + pullRequests = await github.GetPullRequests(); + } + catch (UnauthorizedAccessException ex) + { + Assert.Inconclusive("Try adding your credetials to the AppConfig :)"); + } } } } \ No newline at end of file From f0ac1308ddf27851ff814ae62bce1a44bb4bf1b7 Mon Sep 17 00:00:00 2001 From: Cody Ray Freeman Hoeft Date: Sun, 7 Feb 2016 10:04:43 -0800 Subject: [PATCH 3/6] Store repo directory in Repo and hang on to the rate limiting info from github --- SemDiff.Core/GitHub.cs | 24 +++++-- SemDiff.Core/Repo.cs | 21 ++++-- SemDiff.Test/PullRequestListTest.cs | 102 ++++++++++++++-------------- 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/SemDiff.Core/GitHub.cs b/SemDiff.Core/GitHub.cs index 6efa01c..852585d 100644 --- a/SemDiff.Core/GitHub.cs +++ b/SemDiff.Core/GitHub.cs @@ -31,7 +31,6 @@ public GitHub(string repoOwner, string repoName) this.RepoOwner = repoOwner; this.RepoName = repoName; - this.RequestsRemaining = 1; Client = new HttpClient //TODO: Enable gzip! { BaseAddress = new Uri("https://api.github.com/") @@ -54,15 +53,16 @@ public GitHub(string repoOwner, string repoName, string authUsername, string aut public string RepoName { get; set; } public string RepoOwner { get; set; } public int RequestsRemaining { get; private set; } + public int RequestsLimit { get; private set; } public HttpClient Client { get; private set; } public string RepoFolder { get; set; } - private async void APIError(string content) + /// + /// Makes a request to github to update RequestsRemaining and RequestsLimit + /// + public Task UpdateLimit() { - //TODO: implement Error handling - - //temp - RequestsRemaining = 0; + return HttpGetAsync("/rate_limit"); } /// @@ -79,7 +79,6 @@ private async Task HttpGetAsync(string url) } catch (Exception e) { - APIError(content); throw; } } @@ -88,12 +87,23 @@ private async Task HttpGetAsync(string url) { //Request, but retry once waiting 5 minutes var response = await Extensions.RetryOnce(() => Client.GetAsync(url), TimeSpan.FromMinutes(5)); + IEnumerable headerVal; + if (response.Headers.TryGetValues("X-RateLimit-Limit", out headerVal)) + { + RequestsLimit = int.Parse(headerVal.Single()); + } + if (response.Headers.TryGetValues("X-RateLimit-Remaining", out headerVal)) + { + RequestsRemaining = int.Parse(headerVal.Single()); + } if (!response.IsSuccessStatusCode) { switch (response.StatusCode) { case HttpStatusCode.Unauthorized: throw new UnauthorizedAccessException("Authentication Failure"); + case HttpStatusCode.Forbidden: + throw new UnauthorizedAccessException("Rate Limit Exceeded"); default: throw new NotImplementedException(); } diff --git a/SemDiff.Core/Repo.cs b/SemDiff.Core/Repo.cs index d6cefd1..23dfd5e 100644 --- a/SemDiff.Core/Repo.cs +++ b/SemDiff.Core/Repo.cs @@ -15,6 +15,7 @@ public class Repo { private static readonly ConcurrentDictionary _repoLookup = new ConcurrentDictionary(); public static bool Authentication { get; set; } = true; + public static TimeSpan MaxUpdateInterval { get; set; } = TimeSpan.FromMinutes(5); internal static GitHubConfiguration gitHubConfig = new GitHubConfiguration((AuthenticationSection)ConfigurationManager.GetSection("SemDiff.Core/authentication")); @@ -36,7 +37,7 @@ internal static Repo AddRepo(string directoryPath) if (File.Exists(gitconfig)) { Logger.Info($"Git Config File Found: {gitconfig}"); - return RepoFromConfig(gitconfig); + return RepoFromConfig(directoryPath); } else { @@ -55,10 +56,13 @@ internal static Repo AddRepo(string directoryPath) public string Owner { get; private set; } public string Name { get; private set; } + public string LocalDirectory { get; private set; } public GitHub GitHubApi { get; private set; } + public DateTime LastUpdate { get; internal set; } = DateTime.MinValue; //Old date insures update first time - internal static Repo RepoFromConfig(string gitconfigPath) + internal static Repo RepoFromConfig(string repoDir) { + var gitconfigPath = Path.Combine(repoDir, ".git", "config"); var config = File.ReadAllText(gitconfigPath); var match = _gitHubUrl.Match(config); if (!match.Success) @@ -68,7 +72,7 @@ internal static Repo RepoFromConfig(string gitconfigPath) var owner = match.Groups[3].Value; var name = match.Groups[4].Value; Logger.Debug($"Repo: Owner='{owner}' Name='{name}' Url='{url}'"); - return new Repo(owner, name); + return new Repo(repoDir, owner, name); } /// @@ -79,7 +83,7 @@ internal static void ClearLookup() _repoLookup.Clear(); } - internal Repo(string owner, string name) + internal Repo(string directory, string owner, string name) { if (Authentication) { @@ -93,6 +97,7 @@ internal Repo(string owner, string name) } Owner = owner; Name = name; + LocalDirectory = directory; } /// @@ -107,11 +112,17 @@ public IEnumerable GetRemoteChanges() public void TriggerUpdate() { - throw new NotImplementedException(); + var elapsedSinceUpdate = (DateTime.Now - LastUpdate); + if (elapsedSinceUpdate > MaxUpdateInterval) + { + Update(); + LastUpdate = DateTime.Now; + } } public void Update() { + var pulls = GitHubApi.GetPullRequests(); } } } \ No newline at end of file diff --git a/SemDiff.Test/PullRequestListTest.cs b/SemDiff.Test/PullRequestListTest.cs index b472779..a605dc6 100644 --- a/SemDiff.Test/PullRequestListTest.cs +++ b/SemDiff.Test/PullRequestListTest.cs @@ -18,6 +18,7 @@ public class PullRequestListTest public void TestInit() { github = new GitHub(owner, repository); + github.UpdateLimit().Wait(); var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SemDiff)); if (new FileInfo(appDataFolder).Exists) Directory.Delete(appDataFolder, recursive: true); @@ -33,79 +34,80 @@ public void NewGitHub() [TestMethod] public void PullRequestFromTestRepo() { + if (github.RequestsRemaining == 0) + { + Assert.Inconclusive("Thou hast ran out of requests"); + } var requests = github.GetPullRequests().Result; - if (github.RequestsRemaining != 0) + Assert.AreEqual(4, requests.Count); + var r = requests.First(); + if (r.Number == 4) { - Assert.AreEqual(4, requests.Count); - var r = requests.First(); - if (r.Number == 4) + Assert.AreEqual(r.Locked, false); + Assert.AreEqual(r.State, "open"); + Assert.AreEqual(r.User.Login, "haroldhues"); + Assert.AreEqual(r.Files.Count, 1); + foreach (var f in r.Files) { - Assert.AreEqual(r.Locked, false); - Assert.AreEqual(r.State, "open"); - Assert.AreEqual(r.User.Login, "haroldhues"); - Assert.AreEqual(r.Files.Count, 1); - foreach (var f in r.Files) - { - Assert.AreEqual("Curly-Broccoli/Curly/Logger.cs", f.Filename); - } + Assert.AreEqual("Curly-Broccoli/Curly/Logger.cs", f.Filename); } - else - { - Assert.Fail(); - } - Assert.AreEqual("895d2ca038344aacfbcf3902e978de73a7a763fe", r.Head.Sha); } + else + { + Assert.Fail(); + } + Assert.AreEqual("895d2ca038344aacfbcf3902e978de73a7a763fe", r.Head.Sha); } [TestMethod] public void GetFilesFromGitHub() { + if (github.RequestsRemaining == 0) + { + Assert.Inconclusive("Thou hast ran out of requests"); + } var requests = github.GetPullRequests().Result; var fourWasFound = false; - if (github.RequestsRemaining != 0) + foreach (var r in requests) { - Assert.AreNotEqual(github.RequestsRemaining, 0); - foreach (var r in requests) + github.DownloadFiles(r); + if (r.Number == 4) { - github.DownloadFiles(r); - if (r.Number == 4) + fourWasFound = true; + string line; + int counter = 0; + string[] directoryTokens; + string dir = ""; + foreach (var f in r.Files) { - fourWasFound = true; - string line; - int counter = 0; - string[] directoryTokens; - string dir = ""; - foreach (var f in r.Files) - { - directoryTokens = f.Filename.Split('/'); - dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/SemDiff/semdiffdotnet/curly-broccoli/"; + directoryTokens = f.Filename.Split('/'); + dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/SemDiff/semdiffdotnet/curly-broccoli/"; - dir = Path.Combine(dir, r.Number.ToString(), f.Filename); - } - using (var file = new System.IO.StreamReader(dir)) + dir = Path.Combine(dir, r.Number.ToString(), f.Filename); + } + using (var file = new System.IO.StreamReader(dir)) + { + while ((line = file.ReadLine()) != null) { - while ((line = file.ReadLine()) != null) + if (counter == 6) { - if (counter == 6) - { - var expected = "namespace Curly"; - Assert.AreEqual(line, expected); - } - if (counter == 9) - { - var expected = " /// Utility for logging to an internal list of Log entities"; - Assert.AreEqual(line, expected); - } - counter++; + var expected = "namespace Curly"; + Assert.AreEqual(line, expected); } - - file.Close(); + if (counter == 9) + { + var expected = " /// Utility for logging to an internal list of Log entities"; + Assert.AreEqual(line, expected); + } + counter++; } - Assert.AreEqual(counter, 34); + + file.Close(); } + Assert.AreEqual(counter, 34); } - Assert.AreEqual(fourWasFound, true); } + Assert.AreEqual(fourWasFound, true); } } } \ No newline at end of file From e9aea3c30054c5fff94f1282a0a982d7b257e01d Mon Sep 17 00:00:00 2001 From: Cody Ray Freeman Hoeft Date: Sun, 7 Feb 2016 10:14:59 -0800 Subject: [PATCH 4/6] Fix failing test and add credentials (no access to anything) --- SemDiff.Core/GitHub.cs | 2 +- SemDiff.Core/Repo.cs | 5 ++--- SemDiff.Test/GetRepoTests.cs | 4 ++-- SemDiff.Test/app.config | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/SemDiff.Core/GitHub.cs b/SemDiff.Core/GitHub.cs index 852585d..c1d0de5 100644 --- a/SemDiff.Core/GitHub.cs +++ b/SemDiff.Core/GitHub.cs @@ -31,7 +31,7 @@ public GitHub(string repoOwner, string repoName) this.RepoOwner = repoOwner; this.RepoName = repoName; - Client = new HttpClient //TODO: Enable gzip! + Client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }) { BaseAddress = new Uri("https://api.github.com/") }; diff --git a/SemDiff.Core/Repo.cs b/SemDiff.Core/Repo.cs index 23dfd5e..5b9859e 100644 --- a/SemDiff.Core/Repo.cs +++ b/SemDiff.Core/Repo.cs @@ -37,7 +37,7 @@ internal static Repo AddRepo(string directoryPath) if (File.Exists(gitconfig)) { Logger.Info($"Git Config File Found: {gitconfig}"); - return RepoFromConfig(directoryPath); + return RepoFromConfig(directoryPath, gitconfig); } else { @@ -60,9 +60,8 @@ internal static Repo AddRepo(string directoryPath) public GitHub GitHubApi { get; private set; } public DateTime LastUpdate { get; internal set; } = DateTime.MinValue; //Old date insures update first time - internal static Repo RepoFromConfig(string repoDir) + internal static Repo RepoFromConfig(string repoDir, string gitconfigPath) { - var gitconfigPath = Path.Combine(repoDir, ".git", "config"); var config = File.ReadAllText(gitconfigPath); var match = _gitHubUrl.Match(config); if (!match.Success) diff --git a/SemDiff.Test/GetRepoTests.cs b/SemDiff.Test/GetRepoTests.cs index 3dcfc0a..1ea9ab1 100644 --- a/SemDiff.Test/GetRepoTests.cs +++ b/SemDiff.Test/GetRepoTests.cs @@ -12,7 +12,7 @@ public class GetRepoTests [TestMethod] public void RepoFromConfigTest() { - var repo = Repo.RepoFromConfig("testgitconfig.txt"); + var repo = Repo.RepoFromConfig(".", "testgitconfig.txt"); Assert.AreEqual("dotnet", repo.Owner); Assert.AreEqual("roslyn", repo.Name); } @@ -20,7 +20,7 @@ public void RepoFromConfigTest() [TestMethod] public void RepoFromConfig2Test() { - var repo = Repo.RepoFromConfig("testgitconfig2.txt"); + var repo = Repo.RepoFromConfig(".", "testgitconfig2.txt"); Assert.AreEqual("haroldhues", repo.Owner); Assert.AreEqual("HaroldHues-Public", repo.Name); } diff --git a/SemDiff.Test/app.config b/SemDiff.Test/app.config index 8dfac51..3013eab 100644 --- a/SemDiff.Test/app.config +++ b/SemDiff.Test/app.config @@ -27,7 +27,7 @@ - + \ No newline at end of file From 55bcd978651ec1e2fd77ccd1a3a851466e34defe Mon Sep 17 00:00:00 2001 From: Cody Ray Freeman Hoeft Date: Sun, 7 Feb 2016 11:28:31 -0800 Subject: [PATCH 5/6] Implement GetRemoteChanges and add test for it --- SemDiff.Core/Analysis.cs | 2 ++ SemDiff.Core/GitHub.cs | 37 +++++++++++++++++-- SemDiff.Core/RemoteChanges.cs | 5 +-- SemDiff.Core/RemoteFile.cs | 3 ++ SemDiff.Core/Repo.cs | 21 +++++------ SemDiff.Test/RepoTests.cs | 62 ++++++++++++++++++++++++++++++++ SemDiff.Test/SemDiff.Test.csproj | 7 ++++ SemDiff.Test/packages.config | 1 + 8 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 SemDiff.Test/RepoTests.cs diff --git a/SemDiff.Core/Analysis.cs b/SemDiff.Core/Analysis.cs index 80a0154..1669fd4 100644 --- a/SemDiff.Core/Analysis.cs +++ b/SemDiff.Core/Analysis.cs @@ -15,6 +15,7 @@ public class Analysis /// public static IEnumerable ForFalsePositive(Repo repo, SyntaxTree tree, string filePath) { + var pulls = repo.GetRemoteChanges(); throw new NotImplementedException(); } @@ -23,6 +24,7 @@ public static IEnumerable ForFalsePositive(Repo repo, Syn /// public static IEnumerable ForFalseNegative(Repo repo, SemanticModel semanticModel) { + var pulls = repo.GetRemoteChanges(); throw new NotImplementedException(); } } diff --git a/SemDiff.Core/GitHub.cs b/SemDiff.Core/GitHub.cs index c1d0de5..3354b4f 100644 --- a/SemDiff.Core/GitHub.cs +++ b/SemDiff.Core/GitHub.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using SemDiff.Core.Configuration; @@ -157,20 +158,28 @@ private async Task DownloadFile(int prNum, string path, string sha, bool isAnces { var rawText = await HttpGetAsync($@"https://github.com/{RepoOwner}/{RepoName}/raw/{sha}/{path}"); path = path.Replace('/', Path.DirectorySeparatorChar); - var dir = Path.Combine(RepoFolder, $"{prNum}", path); + string dir = GetPathInCache(RepoFolder, prNum, path, isAncestor); + new FileInfo(dir).Directory.Create(); + File.WriteAllText(dir, rawText); + } + + private static string GetPathInCache(string repofolder, int prNum, string path, bool isAncestor = false) + { + var dir = Path.Combine(repofolder, $"{prNum}", path); if (isAncestor) { dir += ".orig"; } - new FileInfo(dir).Directory.Create(); - File.WriteAllText(dir, rawText); + + return dir; } public class PullRequest { public int Number { get; set; } public string State { get; set; } + public string Title { get; set; } public bool Locked { get; set; } [JsonProperty("updated_at")] @@ -180,6 +189,16 @@ public class PullRequest public HeadBase Head { get; set; } public HeadBase Base { get; set; } public IList Files { get; set; } + + internal RemoteChanges ToRemoteChanges(string repofolder) + { + return new RemoteChanges + { + Date = Updated, + Title = Title, + Files = Files.Where(f => f.Status == GitHub.Files.StatusEnum.Modified).Where(f => f.Filename.Split('.').Last() == "cs").Select(f => f.ToRemoteFile(repofolder, Number)).ToList(), + }; + } } public class Files @@ -189,6 +208,18 @@ public class Files [JsonConverter(typeof(StringEnumConverter))] public StatusEnum Status { get; set; } + internal RemoteFile ToRemoteFile(string repofolder, int num) + { + var baseP = GetPathInCache(repofolder, num, Filename, isAncestor: true); + var fileP = GetPathInCache(repofolder, num, Filename, isAncestor: false); + return new RemoteFile + { + Filename = Filename, + Base = CSharpSyntaxTree.ParseText(File.ReadAllText(baseP), path: baseP), + File = CSharpSyntaxTree.ParseText(File.ReadAllText(fileP), path: fileP) + }; + } + //[JsonProperty("raw_url")] //public string RawUrl { get; set; } public enum StatusEnum diff --git a/SemDiff.Core/RemoteChanges.cs b/SemDiff.Core/RemoteChanges.cs index ddfbd40..acbe82d 100644 --- a/SemDiff.Core/RemoteChanges.cs +++ b/SemDiff.Core/RemoteChanges.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace SemDiff.Core { @@ -9,6 +10,6 @@ public class RemoteChanges //TODO: Abstract to File system { public string Title { get; set; } public IEnumerable Files { get; set; } - public string Date { get; set; } + public DateTime Date { get; set; } } } \ No newline at end of file diff --git a/SemDiff.Core/RemoteFile.cs b/SemDiff.Core/RemoteFile.cs index ef6f47c..0bc2f7f 100644 --- a/SemDiff.Core/RemoteFile.cs +++ b/SemDiff.Core/RemoteFile.cs @@ -8,9 +8,12 @@ public class RemoteFile //TODO: Abstract to File system /// The ancestor that the pull request will be merged with /// public SyntaxTree Base { get; set; } + /// /// The file from the open pull request /// public SyntaxTree File { get; set; } + + public string Filename { get; internal set; } } } \ No newline at end of file diff --git a/SemDiff.Core/Repo.cs b/SemDiff.Core/Repo.cs index 5b9859e..9d415e0 100644 --- a/SemDiff.Core/Repo.cs +++ b/SemDiff.Core/Repo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Configuration; using System.IO; +using System.Linq; using System.Text.RegularExpressions; namespace SemDiff.Core @@ -59,6 +60,7 @@ internal static Repo AddRepo(string directoryPath) public string LocalDirectory { get; private set; } public GitHub GitHubApi { get; private set; } public DateTime LastUpdate { get; internal set; } = DateTime.MinValue; //Old date insures update first time + internal Dictionary RemoteChangesData { get; set; } = new Dictionary(); internal static Repo RepoFromConfig(string repoDir, string gitconfigPath) { @@ -104,24 +106,19 @@ internal Repo(string directory, string owner, string name) /// /// public IEnumerable GetRemoteChanges() - { - TriggerUpdate(); - throw new NotImplementedException(); - } - - public void TriggerUpdate() { var elapsedSinceUpdate = (DateTime.Now - LastUpdate); if (elapsedSinceUpdate > MaxUpdateInterval) { - Update(); + RemoteChangesData.Clear(); + var pulls = GitHubApi.GetPullRequests().Result; + foreach (var p in pulls) + { + RemoteChangesData.Add(p.Number, p.ToRemoteChanges(GitHubApi.RepoFolder)); + } LastUpdate = DateTime.Now; } - } - - public void Update() - { - var pulls = GitHubApi.GetPullRequests(); + return RemoteChangesData.Values; } } } \ No newline at end of file diff --git a/SemDiff.Test/RepoTests.cs b/SemDiff.Test/RepoTests.cs new file mode 100644 index 0000000..4f703d6 --- /dev/null +++ b/SemDiff.Test/RepoTests.cs @@ -0,0 +1,62 @@ +using LibGit2Sharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SemDiff.Core; +using System; +using System.IO; +using System.Linq; + +namespace SemDiff.Test +{ + [TestClass] + public class RepoTests + { + public static Repo CurlyBroccoli { get; set; } + + [ClassInitialize] + public static void ClassInit(TestContext context) + { + var curlyPath = Path.GetFullPath("curly"); + if (Directory.Exists(curlyPath)) + { + SetNormalAttr(new DirectoryInfo(curlyPath)); //http://stackoverflow.com/a/1702920 + Directory.Delete(curlyPath, true); + } + Repository.Clone("https://github.com/semdiffdotnet/curly-broccoli.git", curlyPath); + Repo.Authentication = true; + CurlyBroccoli = Repo.RepoFromConfig(curlyPath, Path.Combine(curlyPath, ".git", "config")); + } + + private static void SetNormalAttr(DirectoryInfo directory) + { + foreach (var subdir in directory.GetDirectories()) + { + SetNormalAttr(subdir); + subdir.Attributes = FileAttributes.Normal; + } + foreach (var file in directory.GetFiles()) + { + file.Attributes = FileAttributes.Normal; + } + } + + [TestMethod] + public void RepoGetChangedFiles() + { + var pulls = CurlyBroccoli.GetRemoteChanges().ToList(); + Assert.AreEqual(4, pulls.Count); + foreach (var p in pulls) + { + Assert.IsNotNull(p.Files); + Assert.IsTrue(p.Files.Count() > 0); + Assert.IsNotNull(p.Title); + Assert.AreNotEqual(default(DateTime), p.Date); + foreach (var f in p.Files) + { + Assert.IsNotNull(f.Base); + Assert.IsNotNull(f.File); + Assert.IsNotNull(f.Filename); + } + } + } + } +} \ No newline at end of file diff --git a/SemDiff.Test/SemDiff.Test.csproj b/SemDiff.Test/SemDiff.Test.csproj index 520aa4c..d515925 100644 --- a/SemDiff.Test/SemDiff.Test.csproj +++ b/SemDiff.Test/SemDiff.Test.csproj @@ -1,5 +1,6 @@  + Debug AnyCPU @@ -40,6 +41,10 @@ + + ..\packages\LibGit2Sharp.0.21.0.176\lib\net40\LibGit2Sharp.dll + True + ..\packages\Microsoft.CodeAnalysis.Common.1.1.1\lib\net45\Microsoft.CodeAnalysis.dll True @@ -136,6 +141,7 @@ + @@ -187,6 +193,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. +