diff --git a/common/SolutionInfo.cs b/common/SolutionInfo.cs index 752416e10..577c0d379 100644 --- a/common/SolutionInfo.cs +++ b/common/SolutionInfo.cs @@ -31,6 +31,6 @@ namespace System { internal static class AssemblyVersionInformation { - internal const string Version = "0.30.11"; + internal const string Version = "0.31.2"; } } diff --git a/octorun/src/api.js b/octorun/src/api.js index 32322ef6a..fcaf96c50 100644 --- a/octorun/src/api.js +++ b/octorun/src/api.js @@ -65,24 +65,24 @@ ApiWrapper.prototype.getOrgs = function (callback) { }; ApiWrapper.prototype.publish = function (name, desc, private, organization, callback) { + var callbackHandler = function (error, result) { + callback(error, (!result) ? null : [result.data.name, result.data.clone_url]); + }; + if (organization) { this.octokit.repos.createForOrg({ org: organization, name: name, description: desc, private: private - }, function (error, result) { - callback(error, (!result) ? null : result.data.clone_url); - }); + }, callbackHandler); } else { this.octokit.repos.create({ name: name, description: desc, private: private - }, function (error, result) { - callback(error, (!result) ? null : result.data.clone_url); - }); + }, callbackHandler); } }; diff --git a/octorun/src/authentication.js b/octorun/src/authentication.js index d724fca8f..43a2de5bb 100644 --- a/octorun/src/authentication.js +++ b/octorun/src/authentication.js @@ -2,9 +2,7 @@ var endOfLine = require('os').EOL; var config = require("./configuration"); var octokitWrapper = require("./octokit"); -var lockedRegex = new RegExp("number of login attempts exceeded", "gi"); var twoFactorRegex = new RegExp("must specify two-factor authentication otp code", "gi"); -var badCredentialsRegex = new RegExp("bad credentials", "gi"); var scopes = ["user", "repo", "gist", "write:public_key"]; @@ -48,12 +46,6 @@ var handleAuthentication = function (username, password, onSuccess, onFailure, t else if (twoFactorRegex.test(err.message)) { onSuccess(password, "2fa"); } - else if (lockedRegex.test(err.message)) { - onFailure("locked") - } - else if (badCredentialsRegex.test(err.message)) { - onFailure("badcredentials") - } else { onFailure(err) } diff --git a/octorun/src/bin/app-login.js b/octorun/src/bin/app-login.js index 98137e7a9..f3484c31b 100644 --- a/octorun/src/bin/app-login.js +++ b/octorun/src/bin/app-login.js @@ -13,15 +13,12 @@ var handleAuthentication = function (username, password, twoFactor) { authentication.handleAuthentication(username, password, function (token, status) { if (status) { output.custom(status, token); - process.exit(); } else { output.success(token); - process.exit(); } }, function (error) { output.error(error); - process.exit(); }, twoFactor); } @@ -41,7 +38,6 @@ if (commander.twoFactor) { } catch (error) { output.error(error); - process.exit(); } } else { @@ -108,7 +104,6 @@ else { } catch (error) { output.error(error); - process.exit(); } }); } diff --git a/octorun/src/bin/app-organizations.js b/octorun/src/bin/app-organizations.js index 401c977ae..480289aa1 100644 --- a/octorun/src/bin/app-organizations.js +++ b/octorun/src/bin/app-organizations.js @@ -14,21 +14,18 @@ try { apiWrapper.getOrgs(function (error, result) { if (error) { output.error(error); - process.exit(); } else { - results = []; + var results = []; for (var i = 0; i < result.length; i++) { results.push(result[i].name); results.push(result[i].login); } output.success(results); - process.exit(); } }); } catch (error) { output.error(error); - process.exit(); } \ No newline at end of file diff --git a/octorun/src/bin/app-publish.js b/octorun/src/bin/app-publish.js index 1b3257da2..5fe602390 100644 --- a/octorun/src/bin/app-publish.js +++ b/octorun/src/bin/app-publish.js @@ -2,6 +2,7 @@ var commander = require("commander"); var package = require('../../package.json') var ApiWrapper = require('../api') var endOfLine = require('os').EOL; +var output = require('../output'); commander .version(package.version) @@ -13,11 +14,8 @@ commander if(!commander.repository) { - process.stdout.write("repository required"); - process.stdout.write(endOfLine); commander.help(); process.exit(-1); - return; } var private = false; @@ -31,46 +29,13 @@ try { apiWrapper.publish(commander.repository, commander.description, private, commander.organization, function (error, result) { if (error) { - process.stdout.write("error"); - process.stdout.write(endOfLine); - - process.stdout.write(""); - process.stdout.write(endOfLine); - - process.stdout.write(""); - process.stdout.write(endOfLine); - - if (error) { - process.stdout.write(error.toString()); - process.stdout.write(endOfLine); - } - - process.exit(); + output.error(error); } else { - process.stdout.write("success"); - process.stdout.write(endOfLine); - - process.stdout.write(commander.repository); - process.stdout.write(endOfLine); - - process.stdout.write(result); - process.stdout.write(endOfLine); - process.exit(); + output.success(result); } }); } catch (error) { - process.stdout.write("error"); - process.stdout.write(endOfLine); - - process.stdout.write(""); - process.stdout.write(endOfLine); - - if (error) { - process.stdout.write(error.toString()); - process.stdout.write(endOfLine); - } - - process.exit(); + output.error(error); } \ No newline at end of file diff --git a/octorun/src/bin/app-usage.js b/octorun/src/bin/app-usage.js index b13a23a88..07bd3b91c 100644 --- a/octorun/src/bin/app-usage.js +++ b/octorun/src/bin/app-usage.js @@ -3,6 +3,7 @@ var package = require('../../package.json') var endOfLine = require('os').EOL; var fs = require('fs'); var util = require('util'); +var output = require('../output'); commander .version(package.version) @@ -50,38 +51,20 @@ if (fileContents && host) { res.on('data', function (d) { if (success) { - process.stdout.write("success"); - process.stdout.write(endOfLine); - process.stdout.write(d); - process.stdout.write(endOfLine); + output.custom("success", d, true); } else { - process.stdout.write("error"); - process.stdout.write(endOfLine); - - process.stdout.write(""); - process.stdout.write(endOfLine); - - process.stdout.write(d); - process.stdout.write(endOfLine); + output.custom("error", "", true); } }); res.on('end', function (d) { - process.exit(success ? 0 : -1); + process.exit(); }); }); req.on('error', function (error) { - process.stdout.write("Error"); - process.stdout.write(endOfLine); - - if (error) { - process.stdout.write(error.toString()); - process.stdout.write(endOfLine); - } - - process.exit(-1); + output.error(error); }); req.write(fileContents); diff --git a/octorun/src/output.js b/octorun/src/output.js index 22b19bdcb..cad4e002b 100644 --- a/octorun/src/output.js +++ b/octorun/src/output.js @@ -1,6 +1,6 @@ var endOfLine = require('os').EOL; -var outputResult = function (status, results, errors) { +var outputResult = function (status, results, errors, preventExit) { process.stdout.write(status); process.stdout.write(endOfLine); @@ -24,8 +24,9 @@ var outputResult = function (status, results, errors) { process.stdout.write(endOfLine); } } - - throw "Unsupported result output"; + else { + throw "Unsupported result output"; + } } if (errors) { @@ -37,26 +38,34 @@ var outputResult = function (status, results, errors) { for (var errorIndex = 0; errorIndex < errors.length; errorIndex++) { var error = errors[errorIndex]; if (typeof error !== 'string') { - throw "Unsupported result output"; + throw "Unsupported error output"; } process.stdout.write(error); process.stdout.write(endOfLine); } } + else if (errors.toString) { + process.stdout.write(errors.toString()); + process.stdout.write(endOfLine); + } else { process.stdout.write(errors); process.stdout.write(endOfLine); } } + + if (!preventExit) { + process.exit(); + } } var outputSuccess = function (results) { outputResult("success", results); } -var outputCustom = function (status, results) { - outputResult(status, results); +var outputCustom = function (status, results, preventExit) { + outputResult(status, results, undefined, preventExit); } var outputError = function (errors) { diff --git a/octorun/version b/octorun/version index 226095832..0af082cdd 100644 --- a/octorun/version +++ b/octorun/version @@ -1 +1 @@ -b91b7b60 \ No newline at end of file +46811135 \ No newline at end of file diff --git a/src/GitHub.Api/Application/ApiClient.cs b/src/GitHub.Api/Application/ApiClient.cs index 10a01e0f3..e6a35e10d 100644 --- a/src/GitHub.Api/Application/ApiClient.cs +++ b/src/GitHub.Api/Application/ApiClient.cs @@ -10,17 +10,6 @@ namespace GitHub.Unity { class ApiClient : IApiClient { - public static IApiClient Create(UriString repositoryUrl, IKeychain keychain, IProcessManager processManager, ITaskManager taskManager, NPath nodeJsExecutablePath, NPath octorunScriptPath) - { - logger.Trace("Creating ApiClient: {0}", repositoryUrl); - - var credentialStore = keychain.Connect(repositoryUrl); - var hostAddress = HostAddress.Create(repositoryUrl); - - return new ApiClient(repositoryUrl, keychain, - processManager, taskManager, nodeJsExecutablePath, octorunScriptPath); - } - private static readonly ILogging logger = LogHelper.GetLogger(); public HostAddress HostAddress { get; } public UriString OriginalUrl { get; } @@ -81,13 +70,13 @@ public async Task GetOrganizations(Action onSuccess, Action onError = null) + public async Task GetCurrentUser(Action onSuccess, Action onError = null) { Guard.ArgumentNotNull(onSuccess, nameof(onSuccess)); try { - await ValidateCurrentUserInternal(); - onSuccess(); + var user = await GetCurrentUser(); + onSuccess(user); } catch (Exception e) { @@ -95,13 +84,6 @@ public async Task ValidateCurrentUser(Action onSuccess, Action onErro } } - public async Task GetCurrentUser(Action callback) - { - Guard.ArgumentNotNull(callback, "callback"); - var user = await GetCurrentUserInternal(); - callback(user); - } - public async Task Login(string username, string password, Action need2faCode, Action result) { Guard.ArgumentNotNull(need2faCode, "need2faCode"); @@ -198,17 +180,33 @@ public async Task ContinueLoginAsync(LoginResult loginResult, Func GetCurrentUser() + { + //TODO: ONE_USER_LOGIN This assumes we only support one login + var keychainConnection = keychain.Connections.FirstOrDefault(); + if (keychainConnection == null) + throw new KeychainEmptyException(); + + var keychainAdapter = await GetValidatedKeychainAdapter(keychainConnection); + + // we can't trust that the system keychain has the username filled out correctly. + // if it doesn't, we need to grab the username from the server and check it + // unfortunately this means that things will be slower when the keychain doesn't have all the info + if (keychainConnection.User == null || keychainAdapter.Credential.Username != keychainConnection.Username) + { + keychainConnection.User = await GetValidatedGitHubUser(keychainConnection, keychainAdapter); + } + return keychainConnection.User; + } + private async Task CreateRepositoryInternal(string repositoryName, string organization, string description, bool isPrivate) { try { logger.Trace("Creating repository"); - await ValidateKeychain(); - await ValidateCurrentUserInternal(); - - var uriString = keychain.Connections.First().Host; - var keychainAdapter = await keychain.Load(uriString); + var user = await GetCurrentUser(); + var keychainAdapter = keychain.Connect(OriginalUrl); var command = new StringBuilder("publish -r \""); command.Append(repositoryName); @@ -233,8 +231,8 @@ private async Task CreateRepositoryInternal(string repositoryN command.Append(" -p"); } - var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, command.ToString(), - user: keychainAdapter.Credential.Username, userToken: keychainAdapter.Credential.Token) + var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, command.ToString(), + user: user.Login, userToken: keychainAdapter.Credential.Token) .Configure(processManager); var ret = await octorunTask.StartAwait(); @@ -247,12 +245,7 @@ private async Task CreateRepositoryInternal(string repositoryN }; } - if (ret.Output.Any()) - { - throw new ApiClientException(string.Join(Environment.NewLine, ret.Output)); - } - - throw new ApiClientException("Publish failed"); + throw new ApiClientException(ret.GetApiErrorMessage() ?? "Publish failed"); } catch (Exception ex) { @@ -267,14 +260,11 @@ private async Task GetOrganizationInternal(Action onSuccess, Act { logger.Trace("Getting Organizations"); - await ValidateKeychain(); - await ValidateCurrentUserInternal(); - - var uriString = keychain.Connections.First().Host; - var keychainAdapter = await keychain.Load(uriString); + var user = await GetCurrentUser(); + var keychainAdapter = keychain.Connect(OriginalUrl); var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, "organizations", - user: keychainAdapter.Credential.Username, userToken: keychainAdapter.Credential.Token) + user: user.Login, userToken: keychainAdapter.Credential.Token) .Configure(processManager); var ret = await octorunTask.StartAsAsync(); @@ -294,12 +284,7 @@ private async Task GetOrganizationInternal(Action onSuccess, Act return; } - if (ret.Output.Any()) - { - throw new ApiClientException(string.Join(Environment.NewLine, ret.Output)); - } - - throw new ApiClientException("Error getting organizations"); + throw new ApiClientException(ret.GetApiErrorMessage() ?? "Error getting organizations"); } catch (Exception ex) { @@ -308,36 +293,53 @@ private async Task GetOrganizationInternal(Action onSuccess, Act } } - private async Task GetCurrentUserInternal() + private async Task GetValidatedKeychainAdapter(Connection keychainConnection) { - try + var keychainAdapter = await keychain.Load(keychainConnection.Host); + if (keychainAdapter == null) + throw new KeychainEmptyException(); + + if (string.IsNullOrEmpty(keychainAdapter.Credential?.Username)) { - logger.Trace("Getting Current User"); - await ValidateKeychain(); + logger.Warning("LoadKeychainInternal: Username is empty"); + throw new TokenUsernameMismatchException(keychainConnection.Username); + } - var uriString = keychain.Connections.First().Host; - var keychainAdapter = await keychain.Load(uriString); + if (keychainAdapter.Credential.Username != keychainConnection.Username) + { + logger.Warning("LoadKeychainInternal: Token username does not match"); + } + return keychainAdapter; + } + + private async Task GetValidatedGitHubUser(Connection keychainConnection, IKeychainAdapter keychainAdapter) + { + try + { var octorunTask = new OctorunTask(taskManager.Token, nodeJsExecutablePath, octorunScriptPath, "validate", - user: keychainAdapter.Credential.Username, userToken: keychainAdapter.Credential.Token) + user: keychainConnection.Username, userToken: keychainAdapter.Credential.Token) .Configure(processManager); var ret = await octorunTask.StartAsAsync(); if (ret.IsSuccess) { + var login = ret.Output[1]; + + if (login != keychainConnection.Username) + { + logger.Trace("LoadKeychainInternal: Api username does not match"); + throw new TokenUsernameMismatchException(keychainConnection.Username, login); + } + return new GitHubUser { Name = ret.Output[0], - Login = ret.Output[1] + Login = login }; } - if (ret.Output.Any()) - { - throw new ApiClientException(string.Join(Environment.NewLine, ret.Output)); - } - - throw new ApiClientException("Error validating current user"); + throw new ApiClientException(ret.GetApiErrorMessage() ?? "Error validating current user"); } catch (KeychainEmptyException) { @@ -350,55 +352,6 @@ private async Task GetCurrentUserInternal() throw; } } - - private async Task ValidateCurrentUserInternal() - { - logger.Trace("Validating User"); - - var apiUser = await GetCurrentUserInternal(); - var apiUsername = apiUser.Login; - - var cachedUsername = keychain.Connections.First().Username; - - if (apiUsername != cachedUsername) - { - throw new TokenUsernameMismatchException(cachedUsername, apiUsername); - } - } - - private async Task LoadKeychainInternal() - { - if (keychain.HasKeys) - { - if (!keychain.NeedsLoad) - { - logger.Trace("LoadKeychainInternal: Previously Loaded"); - return true; - } - - logger.Trace("LoadKeychainInternal: Loading"); - - //TODO: ONE_USER_LOGIN This assumes only ever one user can login - var uriString = keychain.Connections.First().Host; - - var keychainAdapter = await keychain.Load(uriString); - logger.Trace("LoadKeychainInternal: Loaded"); - - return keychainAdapter.Credential.Token != null; - } - - logger.Trace("LoadKeychainInternal: No keys to load"); - - return false; - } - - private async Task ValidateKeychain() - { - if (!await LoadKeychainInternal()) - { - throw new KeychainEmptyException(); - } - } } class GitHubUser @@ -435,7 +388,7 @@ class TokenUsernameMismatchException : ApiClientException public string CachedUsername { get; } public string CurrentUsername { get; } - public TokenUsernameMismatchException(string cachedUsername, string currentUsername) + public TokenUsernameMismatchException(string cachedUsername, string currentUsername = null) { CachedUsername = cachedUsername; CurrentUsername = currentUsername; @@ -448,12 +401,8 @@ protected TokenUsernameMismatchException(SerializationInfo info, StreamingContex class KeychainEmptyException : ApiClientException { public KeychainEmptyException() - { } - public KeychainEmptyException(string message) : base(message) - { } - - public KeychainEmptyException(string message, Exception innerException) : base(message, innerException) - { } + { + } protected KeychainEmptyException(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/src/GitHub.Api/Application/ApplicationManagerBase.cs b/src/GitHub.Api/Application/ApplicationManagerBase.cs index 606faed2b..31bf7f5fd 100644 --- a/src/GitHub.Api/Application/ApplicationManagerBase.cs +++ b/src/GitHub.Api/Application/ApplicationManagerBase.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Collections.Generic; @@ -54,14 +53,35 @@ public void Run(bool firstRun) { Logger.Trace("Run - CurrentDirectory {0}", NPath.CurrentDirectory); - var initEnvironmentTask = new ActionTask(CancellationToken, - (_, path) => InitializeEnvironment(path)) + ITask setExistingEnvironmentPath; + if (Environment.IsMac) + { + setExistingEnvironmentPath = new SimpleProcessTask(CancellationToken, "bash".ToNPath(), "-c \"/usr/libexec/path_helper\"") + .Configure(ProcessManager, dontSetupGit: true) + .Catch(e => true) // make sure this doesn't throw if the task fails + .Then((success, path) => success ? path.Split(new[] { "\"" }, StringSplitOptions.None)[1] : null); + } + else + { + setExistingEnvironmentPath = new FuncTask(CancellationToken, () => null); + } + + setExistingEnvironmentPath.OnEnd += (t, path, success, ex) => { + if (path != null) + { + Logger.Trace("Existing Environment Path Original:{0} Updated:{1}", Environment.Path, path); + Environment.Path = path; + } + }; + + var initEnvironmentTask = new ActionTask(CancellationToken, + (_, state) => InitializeEnvironment(state)) { Affinity = TaskAffinity.UI }; isBusy = true; var octorunInstaller = new OctorunInstaller(Environment, TaskManager); - var setupTask = octorunInstaller.SetupOctorunIfNeeded(); + var setupTask = setExistingEnvironmentPath.Then(octorunInstaller.SetupOctorunIfNeeded()); var initializeGitTask = new FuncTask(CancellationToken, () => { @@ -87,7 +107,8 @@ public void Run(bool firstRun) { if (path.IsInitialized) { - t.GetEndOfChain().Then(initEnvironmentTask, taskIsTopOfChain: true); + t.GetEndOfChain() + .Then(initEnvironmentTask, taskIsTopOfChain: true); return; } Logger.Trace("Using portable git"); @@ -98,7 +119,8 @@ public void Run(bool firstRun) task.Progress(progressReporter.UpdateProgress); task.OnEnd += (thisTask, result, success, exception) => { - thisTask.GetEndOfChain().Then(initEnvironmentTask, taskIsTopOfChain: true); + thisTask.GetEndOfChain() + .Then(initEnvironmentTask, taskIsTopOfChain: true); }; // append installer task to top chain @@ -205,17 +227,23 @@ protected void SetupMetrics(string unityVersion, bool firstRun) /// /// /// - private void InitializeEnvironment(NPath gitExecutablePath) + private void InitializeEnvironment(GitInstaller.GitInstallationState installationState) { isBusy = false; SetupMetrics(); - if (!gitExecutablePath.IsInitialized) + if (!installationState.GitIsValid) { return; } - Environment.GitExecutablePath = gitExecutablePath; + var gitInstallDetails = new GitInstaller.GitInstallDetails(Environment.UserCachePath, Environment.IsWindows); + var isCustomGitExec = installationState.GitExecutablePath != gitInstallDetails.GitExecutablePath; + + Environment.GitExecutablePath = installationState.GitExecutablePath; + Environment.GitLfsExecutablePath = installationState.GitLfsExecutablePath; + + Environment.IsCustomGitExecutable = isCustomGitExec; Environment.User.Initialize(GitClient); var afterGitSetup = new ActionTask(CancellationToken, RestartRepository) @@ -226,7 +254,7 @@ private void InitializeEnvironment(NPath gitExecutablePath) { var credHelperTask = GitClient.GetConfig("credential.helper", GitConfigSource.Global); credHelperTask.OnEnd += (thisTask, credentialHelper, success, exception) => - { + { if (!success || string.IsNullOrEmpty(credentialHelper)) { Logger.Warning("No Windows CredentialHelper found: Setting to wincred"); diff --git a/src/GitHub.Api/Application/IApiClient.cs b/src/GitHub.Api/Application/IApiClient.cs index 438e9bbf5..0ab28ceaf 100644 --- a/src/GitHub.Api/Application/IApiClient.cs +++ b/src/GitHub.Api/Application/IApiClient.cs @@ -14,7 +14,6 @@ Task CreateRepository(string name, string description, bool isPrivate, Task ContinueLogin(LoginResult loginResult, string code); Task LoginAsync(string username, string password, Func need2faCode); Task Logout(UriString host); - Task GetCurrentUser(Action callback); - Task ValidateCurrentUser(Action onSuccess, Action onError = null); + Task GetCurrentUser(Action onSuccess, Action onError = null); } } diff --git a/src/GitHub.Api/Authentication/IKeychain.cs b/src/GitHub.Api/Authentication/IKeychain.cs index dbd067aac..4aa1bcb97 100644 --- a/src/GitHub.Api/Authentication/IKeychain.cs +++ b/src/GitHub.Api/Authentication/IKeychain.cs @@ -16,7 +16,6 @@ public interface IKeychain Connection[] Connections { get; } IList Hosts { get; } bool HasKeys { get; } - bool NeedsLoad { get; } void SetToken(UriString host, string token); event Action ConnectionsChanged; diff --git a/src/GitHub.Api/Authentication/Keychain.cs b/src/GitHub.Api/Authentication/Keychain.cs index 5e93f488c..2b3c933e2 100644 --- a/src/GitHub.Api/Authentication/Keychain.cs +++ b/src/GitHub.Api/Authentication/Keychain.cs @@ -12,6 +12,7 @@ public class Connection { public string Host { get; set; } public string Username { get; set; } + [NonSerialized] internal GitHubUser User; // for json serialization public Connection() @@ -99,7 +100,7 @@ public IKeychainAdapter Connect(UriString host) public async Task Load(UriString host) { - KeychainAdapter keychainAdapter = FindOrCreateAdapter(host); + var keychainAdapter = FindOrCreateAdapter(host); var connection = GetConnection(host); //logger.Trace($@"Loading KeychainAdapter Host:""{host}"" Cached Username:""{cachedConnection.Username}"""); @@ -109,6 +110,7 @@ public async Task Load(UriString host) { logger.Warning("Cannot load host from Credential Manager; removing from cache"); await Clear(host, false); + keychainAdapter = null; } else { @@ -323,6 +325,5 @@ private void UpdateConnections(Connection[] conns) public Connection[] Connections => connections.Values.ToArray(); public IList Hosts => connections.Keys.ToArray(); public bool HasKeys => connections.Any(); - public bool NeedsLoad => HasKeys && !string.IsNullOrEmpty(FindOrCreateAdapter(connections.First().Value.Host).Credential.Token); } } \ No newline at end of file diff --git a/src/GitHub.Api/Authentication/LoginManager.cs b/src/GitHub.Api/Authentication/LoginManager.cs index 269458668..ce0a85ad2 100644 --- a/src/GitHub.Api/Authentication/LoginManager.cs +++ b/src/GitHub.Api/Authentication/LoginManager.cs @@ -193,22 +193,7 @@ private async Task TryLogin( return new LoginResultData(resultCodes, message, host, ret.Output[0]); } - if (ret.IsBadCredentials) - { - return new LoginResultData(LoginResultCodes.Failed, "Bad credentials.", host, ret.Output[0]); - } - - if (ret.IsLocked) - { - return new LoginResultData(LoginResultCodes.LockedOut, "Account locked.", host, ret.Output[0]); - } - - if (ret.Output.Any()) - { - return new LoginResultData(LoginResultCodes.Failed, "Failed.", host, ret.Output[0]); - } - - return new LoginResultData(LoginResultCodes.Failed, "Failed.", host); + return new LoginResultData(LoginResultCodes.Failed, ret.GetApiErrorMessage() ?? "Failed.", host); } } diff --git a/src/GitHub.Api/Cache/CacheContainer.cs b/src/GitHub.Api/Cache/CacheContainer.cs index aa4266b5f..f8de8c283 100644 --- a/src/GitHub.Api/Cache/CacheContainer.cs +++ b/src/GitHub.Api/Cache/CacheContainer.cs @@ -60,13 +60,11 @@ public void CheckAndRaiseEventsIfCacheNewer(CacheType cacheType, CacheUpdateEven private void OnCacheUpdated(CacheType cacheType, DateTimeOffset datetime) { - Logger.Trace("OnCacheUpdated cacheType:{0} datetime:{1}", cacheType, datetime); CacheUpdated.SafeInvoke(cacheType, datetime); } private void OnCacheInvalidated(CacheType cacheType) { - Logger.Trace("OnCacheInvalidated cacheType:{0}", cacheType); CacheInvalidated.SafeInvoke(cacheType); } diff --git a/src/GitHub.Api/Cache/CacheInterfaces.cs b/src/GitHub.Api/Cache/CacheInterfaces.cs index 0d666d684..810db7774 100644 --- a/src/GitHub.Api/Cache/CacheInterfaces.cs +++ b/src/GitHub.Api/Cache/CacheInterfaces.cs @@ -84,20 +84,16 @@ public interface IConfigRemoteDictionary : IDictionary public interface IBranchCache : IManagedCache { - GitBranch[] LocalBranches { get; set; } - GitBranch[] RemoteBranches { get; set; } - GitRemote[] Remotes { get; set; } + GitBranch[] LocalBranches { get; } + GitBranch[] RemoteBranches { get; } + GitRemote[] Remotes { get; } ILocalConfigBranchDictionary LocalConfigBranches { get; } IRemoteConfigBranchDictionary RemoteConfigBranches { get; } IConfigRemoteDictionary ConfigRemotes { get; } - void RemoveLocalBranch(string branch); - void AddLocalBranch(string branch); - void AddRemoteBranch(string remote, string branch); - void RemoveRemoteBranch(string remote, string branch); - void SetRemotes(Dictionary remoteDictionary, Dictionary> branchDictionary); - void SetLocals(Dictionary branchDictionary); + void SetRemotes(Dictionary remoteConfigs, Dictionary> configBranches, GitRemote[] gitRemotes, GitBranch[] gitBranches); + void SetLocals(Dictionary configBranches, GitBranch[] gitBranches); } public interface IRepositoryInfoCacheData diff --git a/src/GitHub.Api/Git/GitBranch.cs b/src/GitHub.Api/Git/GitBranch.cs index d6d38a4f2..857212810 100644 --- a/src/GitHub.Api/Git/GitBranch.cs +++ b/src/GitHub.Api/Git/GitBranch.cs @@ -9,15 +9,13 @@ public struct GitBranch public string name; public string tracking; - public bool isActive; - public GitBranch(string name, string tracking, bool active) + public GitBranch(string name, string tracking) { Guard.ArgumentNotNullOrWhiteSpace(name, "name"); this.name = name; this.tracking = tracking; - this.isActive = active; } public override int GetHashCode() @@ -25,7 +23,6 @@ public override int GetHashCode() int hash = 17; hash = hash * 23 + (name?.GetHashCode() ?? 0); hash = hash * 23 + (tracking?.GetHashCode() ?? 0); - hash = hash * 23 + isActive.GetHashCode(); return hash; } @@ -40,8 +37,7 @@ public bool Equals(GitBranch other) { return String.Equals(name, other.name) && - String.Equals(tracking, other.tracking) && - isActive == other.isActive; + String.Equals(tracking, other.tracking); } public static bool operator ==(GitBranch lhs, GitBranch rhs) @@ -65,11 +61,10 @@ public bool Equals(GitBranch other) public string Name => name; public string Tracking => tracking; - public bool IsActive => isActive; public override string ToString() { - return $"{Name} Tracking? {Tracking} Active? {IsActive}"; + return $"{Name} Tracking? {Tracking}"; } } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs index 23c064d47..ebc337aaa 100644 --- a/src/GitHub.Api/Git/GitClient.cs +++ b/src/GitHub.Api/Git/GitClient.cs @@ -9,7 +9,7 @@ namespace GitHub.Unity { public interface IGitClient { - ITask ValidateGitInstall(NPath path); + ITask ValidateGitInstall(NPath path, bool isCustomGit); ITask Init(IOutputProcessor processor = null); @@ -110,28 +110,31 @@ public GitClient(IEnvironment environment, IProcessManager processManager, Cance this.cancellationToken = cancellationToken; } - public ITask ValidateGitInstall(NPath path) + public ITask ValidateGitInstall(NPath path, bool isCustomGit) { - if (!path.FileExists()) - { - return new FuncTask(TaskEx.FromResult(new ValidateGitInstallResult(false, null, null))); - } - Version gitVersion = null; Version gitLfsVersion = null; - var gitVersionTask = new GitVersionTask(cancellationToken).Configure(processManager, path); - var gitLfsVersionTask = new GitLfsVersionTask(cancellationToken).Configure(processManager, path); - - return gitVersionTask - .Then((result, version) => gitVersion = version) - .Then(gitLfsVersionTask) - .Then((result, version) => gitLfsVersion = version) - .Then(success => new ValidateGitInstallResult(success && + var endTask = new FuncTask(cancellationToken, + () => new ValidateGitInstallResult( gitVersion?.CompareTo(Constants.MinimumGitVersion) >= 0 && gitLfsVersion?.CompareTo(Constants.MinimumGitLfsVersion) >= 0, - gitVersion, gitLfsVersion) - ); + gitVersion, gitLfsVersion)); + + if (path.FileExists()) + { + var gitLfsVersionTask = new GitLfsVersionTask(cancellationToken) + .Configure(processManager, path, dontSetupGit: isCustomGit); + gitLfsVersionTask.OnEnd += (t, v, _, __) => gitLfsVersion = v; + var gitVersionTask = new GitVersionTask(cancellationToken) + .Configure(processManager, path, dontSetupGit: isCustomGit); + gitVersionTask.OnEnd += (t, v, _, __) => gitVersion = v; + + gitVersionTask + .Then(gitLfsVersionTask) + .Finally(endTask); + } + return endTask; } public ITask Init(IOutputProcessor processor = null) diff --git a/src/GitHub.Api/Git/GitConfig.cs b/src/GitHub.Api/Git/GitConfig.cs index dd0cc5f18..b2277295c 100644 --- a/src/GitHub.Api/Git/GitConfig.cs +++ b/src/GitHub.Api/Git/GitConfig.cs @@ -79,17 +79,20 @@ public struct ConfigBranch public string name; public ConfigRemote remote; + public string trackingBranch; public ConfigBranch(string name) { this.name = name; + this.trackingBranch = null; remote = ConfigRemote.Default; } - public ConfigBranch(string name, ConfigRemote? remote) + public ConfigBranch(string name, ConfigRemote? remote, string trackingBranch) { this.name = name; this.remote = remote ?? ConfigRemote.Default; + this.trackingBranch = trackingBranch != null && trackingBranch.StartsWith("refs/heads") ? trackingBranch.Substring("refs/heads".Length + 1) : null; } public override int GetHashCode() @@ -137,6 +140,7 @@ public bool Equals(ConfigBranch other) public bool IsTracking => Remote.HasValue; public string Name => name; + public string TrackingBranch => trackingBranch; public ConfigRemote? Remote => Equals(remote, ConfigRemote.Default) ? (ConfigRemote?) null : remote; @@ -189,7 +193,7 @@ public IEnumerable GetBranches() return groups .Where(x => x.Key == "branch") .SelectMany(x => x.Value) - .Select(x => new ConfigBranch(x.Key, GetRemote(x.Value.TryGetString("remote")))); + .Select(x => new ConfigBranch(x.Key, GetRemote(x.Value.TryGetString("remote")), x.Value.TryGetString("merge"))); } public IEnumerable GetRemotes() @@ -217,7 +221,7 @@ public IEnumerable GetRemotes() .Where(x => x.Key == "branch") .SelectMany(x => x.Value) .Where(x => x.Key == branch) - .Select(x => new ConfigBranch(x.Key,GetRemote(x.Value.TryGetString("remote"))) as ConfigBranch?) + .Select(x => new ConfigBranch(x.Key, GetRemote(x.Value.TryGetString("remote")), x.Value.TryGetString("merge")) as ConfigBranch?) .FirstOrDefault(); } diff --git a/src/GitHub.Api/Git/IRepository.cs b/src/GitHub.Api/Git/IRepository.cs index fa815a152..9e4031ac7 100644 --- a/src/GitHub.Api/Git/IRepository.cs +++ b/src/GitHub.Api/Git/IRepository.cs @@ -8,7 +8,7 @@ namespace GitHub.Unity /// public interface IRepository : IEquatable, IDisposable { - void Initialize(IRepositoryManager repositoryManager, ITaskManager taskManager); + void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager); void Start(); ITask CommitAllFiles(string message, string body); diff --git a/src/GitHub.Api/Git/Repository.cs b/src/GitHub.Api/Git/Repository.cs index 88040611e..4101e5caa 100644 --- a/src/GitHub.Api/Git/Repository.cs +++ b/src/GitHub.Api/Git/Repository.cs @@ -70,14 +70,14 @@ public Repository(NPath localPath, ICacheContainer container) }; } - public void Initialize(IRepositoryManager repositoryManager, ITaskManager taskManager) + public void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager) { //Logger.Trace("Initialize"); - Guard.ArgumentNotNull(repositoryManager, nameof(repositoryManager)); - Guard.ArgumentNotNull(taskManager, nameof(taskManager)); + Guard.ArgumentNotNull(theRepositoryManager, nameof(theRepositoryManager)); + Guard.ArgumentNotNull(theTaskManager, nameof(theTaskManager)); - this.taskManager = taskManager; - this.repositoryManager = repositoryManager; + this.taskManager = theTaskManager; + this.repositoryManager = theRepositoryManager; this.repositoryManager.CurrentBranchUpdated += RepositoryManagerOnCurrentBranchUpdated; this.repositoryManager.GitStatusUpdated += RepositoryManagerOnGitStatusUpdated; this.repositoryManager.GitAheadBehindStatusUpdated += RepositoryManagerOnGitAheadBehindStatusUpdated; @@ -179,7 +179,6 @@ private void CacheHasBeenInvalidated(CacheType cacheType) return; } - Logger.Trace($"CacheInvalidated {cacheType.ToString()}"); switch (cacheType) { case CacheType.Branches: @@ -228,11 +227,9 @@ private void RepositoryManagerOnCurrentBranchUpdated(ConfigBranch? branch, Confi name = null; cloneUrl = null; cacheContainer.RepositoryInfoCache.UpdateData(data); - var n = Name; // force refresh of the Name and CloneUrl property - // update active state in local branches - cacheContainer.BranchCache.LocalBranches = LocalConfigBranches; - // update tracking state in remote branches - cacheContainer.BranchCache.RemoteBranches = RemoteConfigBranches; + + // force refresh of the Name and CloneUrl propertys + var n = Name; }); } @@ -265,21 +262,22 @@ private void RepositoryManagerOnGitLocksUpdated(List gitLocks) taskManager.RunInUI(() => cacheContainer.GitLocksCache.GitLocks = gitLocks); } - private void RepositoryManagerOnRemoteBranchesUpdated(Dictionary remotes, - Dictionary> branches) + private void RepositoryManagerOnRemoteBranchesUpdated(Dictionary remoteConfigs, + Dictionary> remoteConfigBranches) { taskManager.RunInUI(() => { - cacheContainer.BranchCache.SetRemotes(remotes, branches); - cacheContainer.BranchCache.Remotes = ConfigRemotes; - cacheContainer.BranchCache.RemoteBranches = RemoteConfigBranches; + var gitRemotes = remoteConfigs.Values.Select(GetGitRemote).ToArray(); + var gitRemoteBranches = remoteConfigBranches.Values.SelectMany(x => x.Values).Select(GetRemoteGitBranch).ToArray(); + + cacheContainer.BranchCache.SetRemotes(remoteConfigs, remoteConfigBranches, gitRemotes, gitRemoteBranches); }); } - private void RepositoryManagerOnLocalBranchesUpdated(Dictionary branches) + private void RepositoryManagerOnLocalBranchesUpdated(Dictionary localConfigBranchDictionary) { taskManager.RunInUI(() => { - cacheContainer.BranchCache.SetLocals(branches); - cacheContainer.BranchCache.LocalBranches = LocalConfigBranches; + var gitLocalBranches = localConfigBranchDictionary.Values.Select(x => GetLocalGitBranch(CurrentBranchName, x)).ToArray(); + cacheContainer.BranchCache.SetLocals(localConfigBranchDictionary, gitLocalBranches); }); } @@ -288,7 +286,7 @@ private static GitBranch GetLocalGitBranch(string currentBranchName, ConfigBranc var branchName = x.Name; var trackingName = x.IsTracking ? x.Remote.Value.Name + "/" + branchName : "[None]"; var isActive = branchName == currentBranchName; - return new GitBranch(branchName, trackingName, isActive); + return new GitBranch(branchName, trackingName); } @@ -310,7 +308,7 @@ public void Dispose() } - private static GitBranch GetRemoteGitBranch(ConfigBranch x) => new GitBranch(x.Remote.Value.Name + "/" + x.Name, "[None]", false); + private static GitBranch GetRemoteGitBranch(ConfigBranch x) => new GitBranch(x.Remote.Value.Name + "/" + x.Name, "[None]"); private static GitRemote GetGitRemote(ConfigRemote configRemote) => new GitRemote(configRemote.Name, configRemote.Url); public GitRemote[] Remotes => cacheContainer.BranchCache.Remotes; @@ -376,10 +374,6 @@ public string Name internal string DebuggerDisplay => String.Format(CultureInfo.InvariantCulture, "{0} Owner: {1} Name: {2} CloneUrl: {3} LocalPath: {4} Branch: {5} Remote: {6}", GetHashCode(), Owner, Name, CloneUrl, LocalPath, CurrentBranch, CurrentRemote); - - private GitBranch[] RemoteConfigBranches => cacheContainer.BranchCache.RemoteConfigBranches.Values.SelectMany(x => x.Values).Select(GetRemoteGitBranch).ToArray(); - private GitRemote[] ConfigRemotes => cacheContainer.BranchCache.ConfigRemotes.Values.Select(GetGitRemote).ToArray(); - private GitBranch[] LocalConfigBranches => cacheContainer.BranchCache.LocalConfigBranches.Values.Select(x => GetLocalGitBranch(CurrentBranchName, x)).ToArray(); } public interface IUser diff --git a/src/GitHub.Api/Git/RepositoryManager.cs b/src/GitHub.Api/Git/RepositoryManager.cs index 7cb0d413e..a2768154e 100644 --- a/src/GitHub.Api/Git/RepositoryManager.cs +++ b/src/GitHub.Api/Git/RepositoryManager.cs @@ -348,7 +348,7 @@ public void UpdateGitAheadBehindStatus() if (configBranch.HasValue && configBranch.Value.Remote.HasValue) { var name = configBranch.Value.Name; - var trackingName = configBranch.Value.IsTracking ? configBranch.Value.Remote.Value.Name + "/" + name : "[None]"; + var trackingName = configBranch.Value.IsTracking ? configBranch.Value.Remote.Value.Name + "/" + configBranch.Value.TrackingBranch : "[None]"; var task = GitClient.AheadBehindStatus(name, trackingName) .Then((success, status) => @@ -491,6 +491,10 @@ private void WatcherOnLocalBranchesChanged() { Logger.Trace("WatcherOnLocalBranchesChanged"); DataNeedsRefreshing?.Invoke(CacheType.Branches); + // the watcher should tell us what branch has changed so we can fire this only + // when the active branch has changed + DataNeedsRefreshing?.Invoke(CacheType.GitLog); + DataNeedsRefreshing?.Invoke(CacheType.GitAheadBehind); } private void WatcherOnRepositoryCommitted() @@ -520,6 +524,7 @@ private void WatcherOnHeadChanged() Logger.Trace("WatcherOnHeadChanged"); DataNeedsRefreshing?.Invoke(CacheType.RepositoryInfo); DataNeedsRefreshing?.Invoke(CacheType.GitLog); + DataNeedsRefreshing?.Invoke(CacheType.GitAheadBehind); } private void WatcherOnIndexChanged() @@ -577,7 +582,7 @@ private void UpdateRemoteBranches() .Select(x => x.RelativeTo(basedir)) .Select(x => x.ToString(SlashMode.Forward))) { - branchList.Add(branch, new ConfigBranch(branch, remotes[remote])); + branchList.Add(branch, new ConfigBranch(branch, remotes[remote], null)); } remoteBranches.Add(remote, branchList); diff --git a/src/GitHub.Api/Git/TreeData.cs b/src/GitHub.Api/Git/TreeData.cs index 1f5141fb3..ac9e67b2c 100644 --- a/src/GitHub.Api/Git/TreeData.cs +++ b/src/GitHub.Api/Git/TreeData.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Cryptography.X509Certificates; namespace GitHub.Unity { @@ -11,19 +12,22 @@ public interface ITreeData [Serializable] public struct GitBranchTreeData : ITreeData { - public static GitBranchTreeData Default = new GitBranchTreeData(Unity.GitBranch.Default); + public static GitBranchTreeData Default = new GitBranchTreeData(Unity.GitBranch.Default, false); public GitBranch GitBranch; + public bool isActive; - public GitBranchTreeData(GitBranch gitBranch) + public GitBranchTreeData(GitBranch gitBranch, bool isActive) { GitBranch = gitBranch; + this.isActive = isActive; } public override int GetHashCode() { int hash = 17; hash = hash * 23 + GitBranch.GetHashCode(); + hash = hash * 23 + isActive.GetHashCode(); return hash; } @@ -36,7 +40,7 @@ public override bool Equals(object other) public bool Equals(GitBranchTreeData other) { - return GitBranch.Equals(other.GitBranch); + return GitBranch.Equals(other.GitBranch) && IsActive == other.IsActive; } public static bool operator ==(GitBranchTreeData lhs, GitBranchTreeData rhs) @@ -59,7 +63,7 @@ public bool Equals(GitBranchTreeData other) } public string Path => GitBranch.Name; - public bool IsActive => GitBranch.IsActive; + public bool IsActive => isActive; } [Serializable] diff --git a/src/GitHub.Api/Installer/GitInstaller.cs b/src/GitHub.Api/Installer/GitInstaller.cs index 13ac0426f..469b13437 100644 --- a/src/GitHub.Api/Installer/GitInstaller.cs +++ b/src/GitHub.Api/Installer/GitInstaller.cs @@ -14,8 +14,7 @@ class GitInstaller private readonly GitInstallDetails installDetails; private readonly IZipHelper sharpZipLibHelper; - GitInstallationState installationState; - ITask installationTask; + ITask installationTask; public GitInstaller(IEnvironment environment, IProcessManager processManager, ITaskManager taskManager, @@ -28,203 +27,282 @@ public GitInstaller(IEnvironment environment, IProcessManager processManager, this.installDetails = installDetails ?? new GitInstallDetails(environment.UserCachePath, environment.IsWindows); } - public ITask SetupGitIfNeeded() + public ITask SetupGitIfNeeded() { //Logger.Trace("SetupGitIfNeeded"); - - installationTask = new FuncTask(cancellationToken, (success, path) => - { - return path; - }) + GitInstallationState installationState = new GitInstallationState(); + installationTask = new FuncTask(cancellationToken, (success, path) => path) { Name = "Git Installation - Complete" }; installationTask.OnStart += thisTask => thisTask.UpdateProgress(0, 100); installationTask.OnEnd += (thisTask, result, success, exception) => thisTask.UpdateProgress(100, 100); - ITask startTask = null; + ITask startTask = null; if (!environment.IsWindows) { - startTask = new FindExecTask("git", cancellationToken) - .Configure(processManager, false, true); - // we should doublecheck that system git is usable here - installationState = new GitInstallationState + var findTask = new FindExecTask("git", cancellationToken) + .Configure(processManager, dontSetupGit: true) + .Catch(e => true); + findTask.OnEnd += (thisTask, path, success, exception) => { - GitIsValid = true, - GitLfsIsValid = true + // we should doublecheck that system git is usable here + installationState.GitIsValid = success; + if (success) + { + installationState.GitExecutablePath = path; + installationState.GitInstallationPath = path.Resolve().Parent.Parent; + } + }; + findTask.Then(new FindExecTask("git-lfs", cancellationToken) + .Configure(processManager, dontSetupGit: true)) + .Catch(e => true); + findTask.OnEnd += (thisTask, path, success, exception) => + { + installationState.GitLfsIsValid = success; + if (success) + { + // we should doublecheck that system git is usable here + installationState.GitLfsExecutablePath = path; + installationState.GitLfsInstallationPath = path.Resolve().Parent.Parent; + } }; + startTask = findTask.Then(s => installationState); } else { - startTask = new FuncTask(cancellationToken, () => + startTask = new FuncTask(cancellationToken, () => { - installationState = VerifyGitInstallation(); - if (!installationState.GitIsValid && !installationState.GitLfsIsValid) - installationState = GrabZipFromResources(installationState); - else - Logger.Trace("SetupGitIfNeeded: Skipped"); - return installDetails.GitExecutablePath; + return VerifyPortableGitInstallation(); }) { Name = "Git Installation - Extract" }; - } - startTask.OnEnd += (thisTask, path, success, exception) => + startTask = startTask.Then(new FuncTask(cancellationToken, (success, installState) => + { + if (installState.GitIsValid && installState.GitLfsIsValid) + { + return installState; + } + + installState = VerifyZipFiles(installState); + installState = GrabZipFromResourcesIfNeeded(installState); + return installState; + }) + { Name = "Git Installation - Validate" } + ); + + startTask.OnEnd += (thisTask, installState, success, exception) => { - if (!installationState.GitIsValid && !installationState.GitLfsIsValid) + if (installState.GitIsValid && installState.GitLfsIsValid) { - if (!installationState.GitZipExists || !installationState.GitLfsZipExists) - thisTask = thisTask.Then(CreateDownloadTask(installationState)); - thisTask = thisTask.Then(ExtractPortableGit(installationState)); + Logger.Trace("Skipping git installation"); + thisTask.Then(installationTask); + return; } - thisTask.Then(installationTask); + + var downloadZipTask = DownloadZipsIfNeeded(installState); + downloadZipTask.OnEnd += ExtractPortableGit; + thisTask.Then(downloadZipTask); }; return startTask; } - private GitInstallationState VerifyGitInstallation() + private GitInstallationState VerifyPortableGitInstallation() { var state = new GitInstallationState(); - state.GitExists = installDetails.GitExecutablePath.IsInitialized && installDetails.GitExecutablePath.FileExists(); - state.GitLfsExists = installDetails.GitLfsExecutablePath.IsInitialized && installDetails.GitLfsExecutablePath.FileExists(); - state.GitZipExists = installDetails.GitZipPath.FileExists(); - state.GitLfsZipExists = installDetails.GitLfsZipPath.FileExists(); + var gitExists = installDetails.GitExecutablePath.IsInitialized && installDetails.GitExecutablePath.FileExists(); + var gitLfsExists = installDetails.GitLfsExecutablePath.IsInitialized && installDetails.GitLfsExecutablePath.FileExists(); - if (state.GitExists) + if (gitExists) { var actualmd5 = installDetails.GitExecutablePath.CalculateMD5(); var expectedmd5 = environment.IsWindows ? GitInstallDetails.WindowsGitExecutableMD5 : GitInstallDetails.MacGitExecutableMD5; state.GitIsValid = expectedmd5.Equals(actualmd5, StringComparison.InvariantCultureIgnoreCase); - if (!state.GitIsValid) + if (state.GitIsValid) + { + state.GitInstallationPath = installDetails.GitInstallationPath; + state.GitExecutablePath = installDetails.GitExecutablePath; + } + else + { Logger.Trace($"Path {installDetails.GitExecutablePath} has MD5 {actualmd5} expected {expectedmd5}"); + } } else Logger.Trace($"{installDetails.GitExecutablePath} does not exist"); - if (state.GitLfsExists) + if (gitLfsExists) { var actualmd5 = installDetails.GitLfsExecutablePath.CalculateMD5(); var expectedmd5 = environment.IsWindows ? GitInstallDetails.WindowsGitLfsExecutableMD5 : GitInstallDetails.MacGitLfsExecutableMD5; state.GitLfsIsValid = expectedmd5.Equals(actualmd5, StringComparison.InvariantCultureIgnoreCase); - if (!state.GitLfsIsValid) + if (state.GitLfsIsValid) + { + state.GitLfsInstallationPath = installDetails.GitInstallationPath; + state.GitLfsExecutablePath = installDetails.GitLfsExecutablePath; + } + else + { Logger.Trace($"Path {installDetails.GitLfsExecutablePath} has MD5 {actualmd5} expected {expectedmd5}"); + } } else Logger.Trace($"{installDetails.GitLfsExecutablePath} does not exist"); - if (!state.GitZipExists) - Logger.Trace($"{installDetails.GitZipPath} does not exist"); - if (!state.GitLfsZipExists) - Logger.Trace($"{installDetails.GitLfsZipPath} does not exist"); installationTask.UpdateProgress(10, 100); return state; } - private GitInstallationState GrabZipFromResources(GitInstallationState state) + private GitInstallationState VerifyZipFiles(GitInstallationState state) + { + var md5 = AssemblyResources.ToFile(ResourceType.Platform, "git.zip.md5", installDetails.ZipPath, environment); + if (!md5.FileExists() || (installDetails.GitZipPath.FileExists() && !Utils.VerifyFileIntegrity(installDetails.GitZipPath, md5))) + { + installDetails.GitZipPath.DeleteIfExists(); + } + state.GitZipExists = installDetails.GitZipPath.FileExists(); + + md5 = AssemblyResources.ToFile(ResourceType.Platform, "git-lfs.zip.md5", installDetails.ZipPath, environment); + // check whether the git-lfs zip file exists and is valid + if (!md5.FileExists() || (installDetails.GitLfsZipPath.FileExists() && !Utils.VerifyFileIntegrity(installDetails.GitLfsZipPath, md5))) + { + installDetails.GitLfsZipPath.DeleteIfExists(); + } + state.GitLfsZipExists = installDetails.GitLfsZipPath.FileExists(); + installationTask.UpdateProgress(20, 100); + return state; + } + + private GitInstallationState GrabZipFromResourcesIfNeeded(GitInstallationState state) { if (!state.GitZipExists) { AssemblyResources.ToFile(ResourceType.Platform, "git.zip", installDetails.ZipPath, environment); - AssemblyResources.ToFile(ResourceType.Platform, "git.zip.md5", installDetails.ZipPath, environment); } state.GitZipExists = installDetails.GitZipPath.FileExists(); if (!state.GitLfsZipExists) { AssemblyResources.ToFile(ResourceType.Platform, "git-lfs.zip", installDetails.ZipPath, environment); - AssemblyResources.ToFile(ResourceType.Platform, "git-lfs.zip.md5", installDetails.ZipPath, environment); } state.GitLfsZipExists = installDetails.GitLfsZipPath.FileExists(); - installationTask.UpdateProgress(20, 100); + installationTask.UpdateProgress(30, 100); return state; } - private ITask CreateDownloadTask(GitInstallationState state) + private ITask DownloadZipsIfNeeded(GitInstallationState state) { var downloader = new Downloader(); - downloader.QueueDownload(installDetails.GitZipUrl, installDetails.GitZipMd5Url, installDetails.ZipPath); - downloader.QueueDownload(installDetails.GitLfsZipUrl, installDetails.GitLfsZipMd5Url, installDetails.ZipPath); - return downloader.Then((_, data) => + downloader.Catch(e => true); + if (!state.GitIsValid) + downloader.QueueDownload(installDetails.GitZipUrl, installDetails.GitZipMd5Url, installDetails.ZipPath); + if (!state.GitLfsIsValid) + downloader.QueueDownload(installDetails.GitLfsZipUrl, installDetails.GitLfsZipMd5Url, installDetails.ZipPath); + return downloader.Then((success, data) => { state.GitZipExists = installDetails.GitZipPath.FileExists(); state.GitLfsZipExists = installDetails.GitLfsZipPath.FileExists(); installationTask.UpdateProgress(40, 100); - return installDetails.ZipPath; + return state; }); } - private FuncTask ExtractPortableGit(GitInstallationState state) + private void ExtractPortableGit(ITask thisTask, + GitInstallationState state, bool s, Exception exception) { ITask task = null; var tempZipExtractPath = NPath.CreateTempDirectory("git_zip_extract_zip_paths"); - var gitExtractPath = tempZipExtractPath.Combine("git").CreateDirectory(); - if (!state.GitIsValid) + if (state.GitZipExists && !state.GitIsValid) { - ITask unzipTask = new UnzipTask(cancellationToken, installDetails.GitZipPath, gitExtractPath, sharpZipLibHelper, - environment.FileSystem); + var gitExtractPath = tempZipExtractPath.Combine("git").CreateDirectory(); + var unzipTask = new UnzipTask(cancellationToken, installDetails.GitZipPath, + gitExtractPath, sharpZipLibHelper, + environment.FileSystem) + .Catch(e => true); unzipTask.Progress(p => installationTask.UpdateProgress(40 + (long)(20 * p.Percentage), 100, unzipTask.Name)); - unzipTask = unzipTask.Then((s, path) => + unzipTask = unzipTask.Then((success, path) => { - var source = path; var target = installDetails.GitInstallationPath; - target.DeleteIfExists(); - target.EnsureParentDirectoryExists(); - Logger.Trace($"Moving '{source}' to '{target}'"); - source.Move(target); - state.GitExists = installDetails.GitExecutablePath.FileExists(); - state.GitIsValid = s; + if (success) + { + var source = path; + target.DeleteIfExists(); + target.EnsureParentDirectoryExists(); + Logger.Trace($"Moving '{source}' to '{target}'"); + source.Move(target); + state.GitInstallationPath = installDetails.GitInstallationPath; + state.GitExecutablePath = installDetails.GitExecutablePath; + state.GitIsValid = success; + } return target; }); task = unzipTask; } - var gitLfsExtractPath = tempZipExtractPath.Combine("git-lfs").CreateDirectory(); - - if (!state.GitLfsIsValid) + if (state.GitLfsZipExists && !state.GitLfsIsValid) { - ITask unzipTask = new UnzipTask(cancellationToken, installDetails.GitLfsZipPath, gitLfsExtractPath, sharpZipLibHelper, - environment.FileSystem); + var gitLfsExtractPath = tempZipExtractPath.Combine("git-lfs").CreateDirectory(); + var unzipTask = new UnzipTask(cancellationToken, installDetails.GitLfsZipPath, + gitLfsExtractPath, sharpZipLibHelper, + environment.FileSystem) + .Catch(e => true); unzipTask.Progress(p => installationTask.UpdateProgress(60 + (long)(20 * p.Percentage), 100, unzipTask.Name)); - unzipTask = unzipTask.Then((s, path) => + unzipTask = unzipTask.Then((success, path) => { - var source = path.Combine(installDetails.GitLfsExecutable); - var target = installDetails.GetGitLfsExecutablePath(installDetails.GitInstallationPath); - target.DeleteIfExists(); - target.EnsureParentDirectoryExists(); - Logger.Trace($"Moving '{source}' to '{target}'"); - source.Move(target); - state.GitExists = target.FileExists(); - state.GitIsValid = s; + var target = installDetails.GetGitLfsExecutablePath(state.GitInstallationPath); + if (success) + { + var source = path.Combine(installDetails.GitLfsExecutable); + target.DeleteIfExists(); + target.EnsureParentDirectoryExists(); + Logger.Trace($"Moving '{source}' to '{target}'"); + source.Move(target); + state.GitLfsInstallationPath = state.GitInstallationPath; + state.GitLfsExecutablePath = target; + state.GitLfsIsValid = success; + } return target; }); task = task?.Then(unzipTask) ?? unzipTask; } - return task.Finally(new FuncTask(cancellationToken, (success) => + var endTask = new FuncTask(cancellationToken, (success) => { tempZipExtractPath.DeleteIfExists(); - return installDetails.GitExecutablePath; - })); + return state; + }); + + if (task != null) + { + endTask = task.Then(endTask); + } + + thisTask + .Then(endTask) + .Then(installationTask); } - class GitInstallationState + public class GitInstallationState { - public bool GitExists { get; set; } - public bool GitLfsExists { get; set; } public bool GitIsValid { get; set; } public bool GitLfsIsValid { get; set; } public bool GitZipExists { get; set; } public bool GitLfsZipExists { get; set; } + public NPath GitInstallationPath { get; set; } + public NPath GitExecutablePath { get; set; } + public NPath GitLfsInstallationPath { get; set; } + public NPath GitLfsExecutablePath { get; set; } } public class GitInstallDetails { - public const string DefaultGitZipMd5Url = "https://ghfvs-installer.github.com/unity/portable_git/git.zip.md5"; - public const string DefaultGitZipUrl = "https://ghfvs-installer.github.com/unity/portable_git/git.zip"; - public const string DefaultGitLfsZipMd5Url = "https://ghfvs-installer.github.com/unity/portable_git/git-lfs.zip.md5"; - public const string DefaultGitLfsZipUrl = "https://ghfvs-installer.github.com/unity/portable_git/git-lfs.zip"; + public const string DefaultGitZipMd5Url = "https://ghfvs-installer.github.com/unity/git/windows/git.zip.md5"; + public const string DefaultGitZipUrl = "https://ghfvs-installer.github.com/unity/git/windows/git.zip"; + public const string DefaultGitLfsZipMd5Url = "https://ghfvs-installer.github.com/unity/git/windows/git-lfs.zip.md5"; + public const string DefaultGitLfsZipUrl = "https://ghfvs-installer.github.com/unity/git/windows/git-lfs.zip"; public const string GitExtractedMD5 = "e6cfc0c294a2312042f27f893dfc9c0a"; public const string GitLfsExtractedMD5 = "36e3ae968b69fbf42dff72311040d24a"; diff --git a/src/GitHub.Api/Installer/OctorunInstaller.cs b/src/GitHub.Api/Installer/OctorunInstaller.cs index 1ee139c96..8b93c49d8 100644 --- a/src/GitHub.Api/Installer/OctorunInstaller.cs +++ b/src/GitHub.Api/Installer/OctorunInstaller.cs @@ -56,10 +56,10 @@ public ITask SetupOctorunIfNeeded() private NPath GrabZipFromResources() { - if (!installDetails.ZipFile.FileExists()) - { - AssemblyResources.ToFile(ResourceType.Generic, "octorun.zip", installDetails.BaseZipPath, environment); - } + installDetails.ZipFile.DeleteIfExists(); + + AssemblyResources.ToFile(ResourceType.Generic, "octorun.zip", installDetails.BaseZipPath, environment); + return installDetails.ZipFile; } @@ -102,7 +102,7 @@ public class OctorunInstallDetails public const string DefaultZipMd5Url = "https://ghfvs-installer.github.com/unity/octorun/octorun.zip.md5"; public const string DefaultZipUrl = "https://ghfvs-installer.github.com/unity/octorun/octorun.zip"; - public const string PackageVersion = "b91b7b60"; + public const string PackageVersion = "46811135"; private const string PackageName = "octorun"; private const string zipFile = "octorun.zip"; diff --git a/src/GitHub.Api/Installer/ZipHelper.cs b/src/GitHub.Api/Installer/ZipHelper.cs index e2465f19f..57f55ea49 100644 --- a/src/GitHub.Api/Installer/ZipHelper.cs +++ b/src/GitHub.Api/Installer/ZipHelper.cs @@ -19,6 +19,7 @@ public static IZipHelper Instance instance = new ZipHelper(); return instance; } + set { instance = value; } } public bool Extract(string archive, string outFolder, CancellationToken cancellationToken, diff --git a/src/GitHub.Api/Managers/Downloader.cs b/src/GitHub.Api/Managers/Downloader.cs index de079b14f..b4fcbc342 100644 --- a/src/GitHub.Api/Managers/Downloader.cs +++ b/src/GitHub.Api/Managers/Downloader.cs @@ -29,7 +29,9 @@ class Downloader : FuncListTask private readonly List downloaders = new List(); public Downloader() : base(TaskManager.Instance.Token, RunDownloaders) - {} + { + Name = "Downloader"; + } public void QueueDownload(UriString url, UriString md5Url, NPath targetDirectory) { @@ -70,6 +72,7 @@ class PairDownloader private int finishedTaskCount; private volatile bool isSuccessful = true; private volatile Exception exception; + private DownloadData result; public PairDownloader() { @@ -83,6 +86,8 @@ public Task Run() { foreach (var task in queuedTasks) task.Start(); + if (queuedTasks.Count == 0) + DownloadComplete(result); return aggregateDownloads.Task; } @@ -90,7 +95,7 @@ public Task QueueDownload(UriString url, UriString md5Url, NPath t { var destinationFile = targetDirectory.Combine(url.Filename); var destinationMd5 = targetDirectory.Combine(md5Url.Filename); - var result = new DownloadData(url, destinationFile); + result = new DownloadData(url, destinationFile); Action, NPath, bool, Exception> verifyDownload = (t, res, success, ex) => { @@ -123,30 +128,28 @@ public Task QueueDownload(UriString url, UriString md5Url, NPath t if (!md5Exists) { - destinationMd5.DeleteIfExists(); - var md5Download = new DownloadTask(cancellationToken, fs, md5Url, targetDirectory) - .Catch(e => DownloadFailed(result, e)); - md5Download.OnEnd += verifyDownload; + var md5Download = DownloadFile(md5Url, targetDirectory, result, verifyDownload); + md5Download.OnStart += _ => DownloadStart?.Invoke(result); queuedTasks.Add(md5Download); } if (!fileExists) { - var fileDownload = new DownloadTask(cancellationToken, fs, url, targetDirectory) - .Catch(e => DownloadFailed(result, e)); - fileDownload.OnStart += _ => DownloadStart?.Invoke(result); - fileDownload.OnEnd += verifyDownload; + var fileDownload = DownloadFile(url, targetDirectory, result, verifyDownload); + if (md5Exists) // only invoke DownloadStart if it hasn't been invoked before in the md5 download + fileDownload.OnStart += _ => DownloadStart?.Invoke(result); queuedTasks.Add(fileDownload); } - - if (fileExists && md5Exists) - { - var verification = new FuncTask(cancellationToken, () => destinationFile); - verification.OnEnd += verifyDownload; - queuedTasks.Add(verification); - } return aggregateDownloads.Task; } + + private ITask DownloadFile(UriString url, NPath targetDirectory, DownloadData result, Action, NPath, bool, Exception> verifyDownload) + { + var download = new DownloadTask(cancellationToken, fs, url, targetDirectory) + .Catch(e => { DownloadFailed(result, e); return true; }); + download.OnEnd += verifyDownload; + return download; + } } public static bool Download(ILogging logger, UriString url, diff --git a/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs index cca8ed4ed..320ea9435 100644 --- a/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs @@ -36,7 +36,7 @@ public override void LineReceived(string line) trackingName = proc.ReadChunk('[', ']'); } - var branch = new GitBranch(name, trackingName, active); + var branch = new GitBranch(name, trackingName); RaiseOnEntry(branch); } diff --git a/src/GitHub.Api/OutputProcessors/ProcessManager.cs b/src/GitHub.Api/OutputProcessors/ProcessManager.cs index c85c94367..23d8c009c 100644 --- a/src/GitHub.Api/OutputProcessors/ProcessManager.cs +++ b/src/GitHub.Api/OutputProcessors/ProcessManager.cs @@ -1,5 +1,6 @@ using GitHub.Logging; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -29,7 +30,18 @@ public T Configure(T processTask, NPath? executable = null, string arguments bool dontSetupGit = false) where T : IProcess { - executable = executable ?? processTask.ProcessName?.ToNPath() ?? environment.GitExecutablePath; + if (executable == null) + { + if (processTask.ProcessName?.ToNPath() != null) + { + executable = processTask.ProcessName.ToNPath(); + } + else + { + executable = environment.GitExecutablePath; + dontSetupGit = environment.IsCustomGitExecutable; + } + } //If this null check fails, be sure you called Configure() on your task Guard.ArgumentNotNull(executable, nameof(executable)); @@ -87,7 +99,7 @@ public void RunCommandLineWindow(NPath workingDirectory) var envVars = startInfo.EnvironmentVariables; var scriptContents = new[] { $"cd \"{envVars["GHU_WORKINGDIR"]}\"", - $"PATH=\"{envVars["GHU_FULLPATH"]}\":$PATH /bin/bash" + $"PATH=\"{envVars["GHU_FULLPATH"]}\" /bin/bash" }; environment.FileSystem.WriteAllLines(envVarFile, scriptContents); Mono.Unix.Native.Syscall.chmod(envVarFile, (Mono.Unix.Native.FilePermissions)493); // -rwxr-xr-x mode (0755) diff --git a/src/GitHub.Api/Platform/DefaultEnvironment.cs b/src/GitHub.Api/Platform/DefaultEnvironment.cs index 253052ca2..1fd414d0b 100644 --- a/src/GitHub.Api/Platform/DefaultEnvironment.cs +++ b/src/GitHub.Api/Platform/DefaultEnvironment.cs @@ -13,6 +13,7 @@ public class DefaultEnvironment : IEnvironment private static bool? onMac; private NPath gitExecutablePath; + private NPath gitLfsExecutablePath; private NPath nodeJsExecutablePath; private NPath octorunScriptPath; @@ -132,7 +133,8 @@ public string GetEnvironmentVariable(string variable) public NPath ExtensionInstallPath { get; set; } public NPath UserCachePath { get; set; } public NPath SystemCachePath { get; set; } - public NPath Path => Environment.GetEnvironmentVariable("PATH").ToNPath(); + public string Path { get; set; } = Environment.GetEnvironmentVariable("PATH"); + public string NewLine => Environment.NewLine; public NPath OctorunScriptPath { @@ -147,6 +149,9 @@ public NPath OctorunScriptPath octorunScriptPath = value; } } + + public bool IsCustomGitExecutable { get; set; } + public NPath GitExecutablePath { get { return gitExecutablePath; } @@ -159,6 +164,17 @@ public NPath GitExecutablePath GitInstallPath = GitExecutablePath.Resolve().Parent.Parent; } } + + public NPath GitLfsExecutablePath + { + get { return gitLfsExecutablePath; } + set + { + gitLfsExecutablePath = value; + GitLfsInstallPath = gitLfsExecutablePath.IsInitialized ? gitLfsExecutablePath.Parent : NPath.Default; + } + } + public NPath NodeJsExecutablePath { get @@ -173,6 +189,7 @@ public NPath NodeJsExecutablePath } } public NPath GitInstallPath { get; private set; } + public NPath GitLfsInstallPath { get; private set; } public NPath RepositoryPath { get; private set; } public ICacheContainer CacheContainer { get; private set; } public IRepository Repository { get; set; } diff --git a/src/GitHub.Api/Platform/IEnvironment.cs b/src/GitHub.Api/Platform/IEnvironment.cs index 2a8d85105..f25161da7 100644 --- a/src/GitHub.Api/Platform/IEnvironment.cs +++ b/src/GitHub.Api/Platform/IEnvironment.cs @@ -10,8 +10,9 @@ public interface IEnvironment string GetEnvironmentVariable(string v); string GetSpecialFolder(Environment.SpecialFolder folder); - NPath Path { get; } + string Path { get; set; } string NewLine { get; } + bool IsCustomGitExecutable { get; set; } NPath GitExecutablePath { get; set; } NPath NodeJsExecutablePath { get; } NPath OctorunScriptPath { get; set; } @@ -34,5 +35,7 @@ public interface IEnvironment IRepository Repository { get; set; } string ExecutableExtension { get; } ICacheContainer CacheContainer { get; } + NPath GitLfsInstallPath { get; } + NPath GitLfsExecutablePath { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Api/Platform/ProcessEnvironment.cs b/src/GitHub.Api/Platform/ProcessEnvironment.cs index adc606e9b..b78c5fdbb 100644 --- a/src/GitHub.Api/Platform/ProcessEnvironment.cs +++ b/src/GitHub.Api/Platform/ProcessEnvironment.cs @@ -1,5 +1,6 @@ using GitHub.Logging; using System; +using System.Collections.Generic; using System.Diagnostics; namespace GitHub.Unity @@ -21,61 +22,80 @@ public void Configure(ProcessStartInfo psi, NPath workingDirectory, bool dontSet psi.EnvironmentVariables["HOME"] = NPath.HomeDirectory; psi.EnvironmentVariables["TMP"] = psi.EnvironmentVariables["TEMP"] = NPath.SystemTemp; - // if we don't know where git is, then there's nothing else to configure - if (!Environment.GitInstallPath.IsInitialized || dontSetupGit) - return; + var path = Environment.Path; + psi.EnvironmentVariables["GHU_WORKINGDIR"] = workingDirectory; + if (dontSetupGit) + { + psi.EnvironmentVariables["GHU_FULLPATH"] = path; + psi.EnvironmentVariables["PATH"] = path; + return; + } Guard.ArgumentNotNull(psi, "psi"); - // We need to essentially fake up what git-cmd.bat does - - var gitPathRoot = Environment.GitInstallPath; - var gitLfsPath = Environment.GitInstallPath; - var gitExecutableDir = Environment.GitExecutablePath.Parent; // original path to git (might be different from install path if it's a symlink) - - // Paths to developer tools such as msbuild.exe - //var developerPaths = StringExtensions.JoinForAppending(";", developerEnvironment.GetPaths()); - var developerPaths = ""; + var pathEntries = new List(); + string separator = Environment.IsWindows ? ";" : ":"; - //TODO: Remove with Git LFS Locking becomes standard - psi.EnvironmentVariables["GITLFSLOCKSENABLED"] = "1"; - - string path; - var baseExecPath = gitPathRoot; - var binPath = baseExecPath; - if (Environment.IsWindows) + if (Environment.GitInstallPath.IsInitialized) { - if (baseExecPath.DirectoryExists("mingw32")) - baseExecPath = baseExecPath.Combine("mingw32"); + var gitPathRoot = Environment.GitInstallPath; + var gitExecutableDir = Environment.GitExecutablePath.Parent; // original path to git (might be different from install path if it's a symlink) + + var baseExecPath = gitPathRoot; + var binPath = baseExecPath; + if (Environment.IsWindows) + { + if (baseExecPath.DirectoryExists("mingw32")) + baseExecPath = baseExecPath.Combine("mingw32"); + else + baseExecPath = baseExecPath.Combine("mingw64"); + binPath = baseExecPath.Combine("bin"); + } + + var execPath = baseExecPath.Combine("libexec", "git-core"); + if (!execPath.DirectoryExists()) + execPath = NPath.Default; + + if (Environment.IsWindows) + { + pathEntries.AddRange(new[] { gitPathRoot.Combine("cmd").ToString(), gitPathRoot.Combine("usr", "bin") }); + } else - baseExecPath = baseExecPath.Combine("mingw64"); - binPath = baseExecPath.Combine("bin"); + { + pathEntries.Add(gitExecutableDir.ToString()); + } + + if (execPath.IsInitialized) + pathEntries.Add(execPath); + pathEntries.Add(binPath); + + // we can only set this env var if there is a libexec/git-core. git will bypass internally bundled tools if this env var + // is set, which will break Apple's system git on certain tools (like osx-credentialmanager) + if (execPath.IsInitialized) + psi.EnvironmentVariables["GIT_EXEC_PATH"] = execPath.ToString(); } - var execPath = baseExecPath.Combine("libexec", "git-core"); - if (!execPath.DirectoryExists()) - execPath = NPath.Default; - - if (Environment.IsWindows) + if (Environment.GitLfsInstallPath.IsInitialized && Environment.GitInstallPath != Environment.GitLfsInstallPath) { - var userPath = @"C:\windows\system32;C:\windows"; - path = $"{gitPathRoot}\\cmd;{gitPathRoot}\\usr\\bin;{execPath};{binPath};{gitLfsPath};{userPath}{developerPaths}"; - } - else - { - path = $"{gitExecutableDir}:{binPath}:{execPath}:{gitLfsPath}:{Environment.Path}:{developerPaths}"; + pathEntries.Add(Environment.GitLfsInstallPath); } - if (execPath.IsInitialized) - psi.EnvironmentVariables["GIT_EXEC_PATH"] = execPath.ToString(); + pathEntries.Add("END"); + + path = String.Join(separator, pathEntries.ToArray()) + separator + path; - psi.EnvironmentVariables["PATH"] = path; psi.EnvironmentVariables["GHU_FULLPATH"] = path; - psi.EnvironmentVariables["GHU_WORKINGDIR"] = workingDirectory; + psi.EnvironmentVariables["PATH"] = path; - psi.EnvironmentVariables["PLINK_PROTOCOL"] = "ssh"; - psi.EnvironmentVariables["TERM"] = "msys"; + //TODO: Remove with Git LFS Locking becomes standard + psi.EnvironmentVariables["GITLFSLOCKSENABLED"] = "1"; + + if (Environment.IsWindows) + { + psi.EnvironmentVariables["PLINK_PROTOCOL"] = "ssh"; + psi.EnvironmentVariables["TERM"] = "msys"; + } var httpProxy = Environment.GetEnvironmentVariable("HTTP_PROXY"); if (!String.IsNullOrEmpty(httpProxy)) diff --git a/src/GitHub.Api/Resources/octorun.zip b/src/GitHub.Api/Resources/octorun.zip index 24097006a..8e580d84c 100644 --- a/src/GitHub.Api/Resources/octorun.zip +++ b/src/GitHub.Api/Resources/octorun.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcdf06517450ccbad14e1bea863387bef84b24d3709eec50c371f918839606e6 -size 212554 +oid sha256:88514fe0aa33af8ccf81f31f4ca2d4e3b2e08df2f890bb1f18f8d9d5409f7a80 +size 219645 diff --git a/src/GitHub.Api/Resources/octorun.zip.md5 b/src/GitHub.Api/Resources/octorun.zip.md5 index b10619984..e4f79b239 100644 --- a/src/GitHub.Api/Resources/octorun.zip.md5 +++ b/src/GitHub.Api/Resources/octorun.zip.md5 @@ -1 +1 @@ -7cdaa49008b8c996343e07670100bce2 \ No newline at end of file +070a4561bf70031ad54ca5884f97f546 \ No newline at end of file diff --git a/src/GitHub.Api/Tasks/OctorunTask.cs b/src/GitHub.Api/Tasks/OctorunTask.cs index 1a0e04c58..f64fcc0cc 100644 --- a/src/GitHub.Api/Tasks/OctorunTask.cs +++ b/src/GitHub.Api/Tasks/OctorunTask.cs @@ -2,13 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Reflection; +using System.Text.RegularExpressions; using System.Threading; -using GitHub.Logging; namespace GitHub.Unity { - class OctorunTaskOutputProcessor : BaseOutputProcessor + class OctorunResultOutputProcessor : BaseOutputProcessor { private int lineCount; private string status; @@ -68,7 +67,7 @@ public OctorunTask(CancellationToken token, NPath pathToNodeJs, NPath pathToOcto string user = null, string userToken = null, IOutputProcessor processor = null) - : base(token, processor ?? new OctorunTaskOutputProcessor()) + : base(token, processor ?? new OctorunResultOutputProcessor()) { this.clientId = clientId; this.clientSecret = clientSecret; @@ -114,9 +113,6 @@ public override void Configure(ProcessStartInfo psi) class OctorunResult { - public string Status { get; } - public string[] Output { get; } - public OctorunResult() { Status = "error"; @@ -129,10 +125,26 @@ public OctorunResult(string status, string[] output) Output = output; } + public string Status { get; } + public string[] Output { get; } public bool IsSuccess => Status.Equals("success", StringComparison.InvariantCultureIgnoreCase); public bool IsError => Status.Equals("error", StringComparison.InvariantCultureIgnoreCase); public bool IsTwoFactorRequired => Status.Equals("2fa", StringComparison.InvariantCultureIgnoreCase); - public bool IsLocked => Output.First().Equals("locked", StringComparison.InvariantCultureIgnoreCase); - public bool IsBadCredentials => Output.First().Equals("badcredentials", StringComparison.InvariantCultureIgnoreCase); } -} \ No newline at end of file + + static class OctorunResultExtensions + { + private static Regex ApiErrorMessageRegex = new Regex(@"\""message\"":\""(.*?)\""", RegexOptions.Compiled); + + internal static string GetApiErrorMessage(this OctorunResult octorunResult) + { + if (!octorunResult.IsError || !octorunResult.Output.Any()) + { + return null; + } + + var match = ApiErrorMessageRegex.Match(octorunResult.Output[0]); + return match.Success ? match.Groups[1].Value : octorunResult.Output[0]; + } + } +} diff --git a/src/GitHub.Api/Tasks/ProcessTask.cs b/src/GitHub.Api/Tasks/ProcessTask.cs index dccca3146..2debe995a 100644 --- a/src/GitHub.Api/Tasks/ProcessTask.cs +++ b/src/GitHub.Api/Tasks/ProcessTask.cs @@ -350,7 +350,7 @@ public override string ToString() public Process Process { get; set; } public int ProcessId { get { return Process.Id; } } - public override bool Successful { get { return Task.Status == TaskStatus.RanToCompletion && Process.ExitCode == 0; } } + public override bool Successful { get { return !taskFailed && Task.Status == TaskStatus.RanToCompletion && Process.ExitCode == 0; } } public StreamWriter StandardInput { get { return wrapper?.Input; } } public virtual string ProcessName { get; protected set; } public virtual string ProcessArguments { get; } diff --git a/src/GitHub.Api/Tasks/TaskBase.cs b/src/GitHub.Api/Tasks/TaskBase.cs index e70f20035..b959c530e 100644 --- a/src/GitHub.Api/Tasks/TaskBase.cs +++ b/src/GitHub.Api/Tasks/TaskBase.cs @@ -316,7 +316,6 @@ public virtual ITask Start(TaskScheduler scheduler) { //Logger.Trace($"Starting {Affinity} {ToString()}"); Task.Start(scheduler); - SetContinuation(); } return this; } @@ -551,7 +550,20 @@ protected TaskBase(Task task) public override T Then(T continuation, TaskRunOptions runOptions = TaskRunOptions.OnSuccess, bool taskIsTopOfChain = false) { - return base.Then(continuation, runOptions, taskIsTopOfChain); + var nextTask = base.Then(continuation, runOptions, taskIsTopOfChain); + var nextTaskBase = ((TaskBase)(object)nextTask); + // if the current task has a fault handler that matches this signature, attach it to the chain we're appending + if (finallyHandler != null) + { + TaskBase endOfChainTask = (TaskBase)nextTaskBase.GetEndOfChain(); + while (endOfChainTask != this && endOfChainTask != null) + { + if (endOfChainTask is TaskBase) + ((TaskBase)endOfChainTask).finallyHandler += finallyHandler; + endOfChainTask = endOfChainTask.DependsOn; + } + } + return nextTask; } /// diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs index 794f9ef8b..65c50b64e 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationCache.cs @@ -480,174 +480,34 @@ sealed class BranchesCache : ManagedCacheBase, IBranchCache public BranchesCache() : base(CacheType.Branches) { } - public GitBranch[] LocalBranches - { - get { return localBranches; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} localBranches:{1}", now, value); - - var localBranchesIsNull = localBranches == null; - var valueIsNull = value == null; - - if (localBranchesIsNull != valueIsNull || - !localBranchesIsNull && !localBranches.SequenceEqual(value)) - { - localBranches = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public GitBranch[] RemoteBranches - { - get { return remoteBranches; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} remoteBranches:{1}", now, value); - - var remoteBranchesIsNull = remoteBranches == null; - var valueIsNull = value == null; - - if (remoteBranchesIsNull != valueIsNull || - !remoteBranchesIsNull && !remoteBranches.SequenceEqual(value)) - { - remoteBranches = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public GitRemote[] Remotes - { - get { return remotes; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} remotes:{1}", now, value); - - var remotesIsNull = remotes == null; - var valueIsNull = value == null; - - if (remotesIsNull != valueIsNull || - !remotesIsNull && !remotes.SequenceEqual(value)) - { - remotes = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public void RemoveLocalBranch(string branch) - { - if (LocalConfigBranches.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - LocalConfigBranches.Remove(branch); - Logger.Trace("RemoveLocalBranch {0} branch:{1} ", now, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is not found", branch); - } - } - - public void AddLocalBranch(string branch) - { - if (!LocalConfigBranches.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - LocalConfigBranches.Add(branch, new ConfigBranch(branch)); - Logger.Trace("AddLocalBranch {0} branch:{1} ", now, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is already present", branch); - } - } - - public void AddRemoteBranch(string remote, string branch) - { - Dictionary branchList; - if (RemoteConfigBranches.TryGetValue(remote, out branchList)) - { - if (!branchList.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - branchList.Add(branch, new ConfigBranch(branch, ConfigRemotes[remote])); - Logger.Trace("AddRemoteBranch {0} remote:{1} branch:{2} ", now, remote, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is already present in Remote {1}", branch, remote); - } - } - else - { - Logger.Warning("Remote {0} is not found", remote); - } - } - - public void RemoveRemoteBranch(string remote, string branch) - { - Dictionary branchList; - if (RemoteConfigBranches.TryGetValue(remote, out branchList)) - { - if (branchList.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - branchList.Remove(branch); - Logger.Trace("RemoveRemoteBranch {0} remote:{1} branch:{2} ", now, remote, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is not found in Remote {1}", branch, remote); - } - } - else - { - Logger.Warning("Remote {0} is not found", remote); - } - } - - public void SetRemotes(Dictionary remoteDictionary, Dictionary> branchDictionary) + public void SetRemotes(Dictionary remoteConfigs, Dictionary> configBranches, GitRemote[] gitRemotes, GitBranch[] gitBranches) { var now = DateTimeOffset.Now; - configRemotes = new ConfigRemoteDictionary(remoteDictionary); - remoteConfigBranches = new RemoteConfigBranchDictionary(branchDictionary); + configRemotes = new ConfigRemoteDictionary(remoteConfigs); + remoteConfigBranches = new RemoteConfigBranchDictionary(configBranches); + remotes = gitRemotes; + remoteBranches = gitBranches; + Logger.Trace("SetRemotes {0}", now); SaveData(now, true); } - public void SetLocals(Dictionary branchDictionary) + public void SetLocals(Dictionary configBranches, GitBranch[] gitBranches) { var now = DateTimeOffset.Now; - localConfigBranches = new LocalConfigBranchDictionary(branchDictionary); - Logger.Trace("SetRemotes {0}", now); + localConfigBranches = new LocalConfigBranchDictionary(configBranches); + localBranches = gitBranches; + + Logger.Trace("SetLocals {0}", now); SaveData(now, true); } public ILocalConfigBranchDictionary LocalConfigBranches { get { return localConfigBranches; } } public IRemoteConfigBranchDictionary RemoteConfigBranches { get { return remoteConfigBranches; } } public IConfigRemoteDictionary ConfigRemotes { get { return configRemotes; } } + public GitBranch[] LocalBranches { get { return localBranches; } } + public GitBranch[] RemoteBranches { get { return remoteBranches; } } + public GitRemote[] Remotes { get { return remotes; } } public override TimeSpan DataTimeout { get { return TimeSpan.FromDays(1); } } } @@ -671,7 +531,7 @@ public List Log var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} gitLog:{1}", now, value); + Logger.Trace("{0} Updating Log: current:{1} new:{2}", now, log.Count, value.Count); if (!log.SequenceEqual(value)) { @@ -707,8 +567,7 @@ public int Ahead var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} ahead:{1}", now, value); - + Logger.Trace("{0} Updating Ahead: current:{1} new:{2}", now, ahead, value); if (ahead != value) { ahead = value; @@ -731,7 +590,7 @@ public int Behind var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} behind:{1}", now, value); + Logger.Trace("{0} Updating Behind: current:{1} new:{2}", now, behind, value); if (behind != value) { @@ -766,7 +625,7 @@ public List Entries var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} entries:{1}", now, value.Count); + Logger.Trace("{0} Updating Entries: current:{1} new:{2}", now, entries.Count, value.Count); if (!entries.SequenceEqual(value)) { @@ -801,7 +660,7 @@ public List GitLocks var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} gitLocks:{1}", now, value); + Logger.Trace("{0} Updating GitLocks: current:{1} new:{2}", now, gitLocks.Count, value.Count); if (!gitLocks.SequenceEqual(value)) { @@ -837,7 +696,7 @@ public string Name var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} Name:{1}", now, value); + Logger.Trace("{0} Updating Name: current:{1} new:{2}", now, gitName, value); if (gitName != value) { @@ -861,7 +720,7 @@ public string Email var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} Email:{1}", now, value); + Logger.Trace("{0} Updating Email: current:{1} new:{2}", now, gitEmail, value); if (gitEmail != value) { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs index 73b38da86..cf6f12222 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Services/AuthenticationService.cs @@ -10,7 +10,7 @@ class AuthenticationService public AuthenticationService(UriString host, IKeychain keychain, NPath nodeJsExecutablePath, NPath octorunExecutablePath) { - client = ApiClient.Create(host, keychain, EntryPoint.ApplicationManager.ProcessManager, EntryPoint.ApplicationManager.TaskManager, nodeJsExecutablePath, octorunExecutablePath); + client = new ApiClient(host, keychain, EntryPoint.ApplicationManager.ProcessManager, EntryPoint.ApplicationManager.TaskManager, nodeJsExecutablePath, octorunExecutablePath); } public void Login(string username, string password, Action twofaRequired, Action authResult) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs index 09d347d9e..f0ef7adc8 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BranchesView.cs @@ -50,6 +50,12 @@ class BranchesView : Subview [SerializeField] private CacheUpdateEvent lastLocalAndRemoteBranchListChangedEvent; [NonSerialized] private bool localAndRemoteBranchListHasUpdate; + [SerializeField] private CacheUpdateEvent lastCurrentBranchAndRemoteChange; + [NonSerialized] private bool currentBranchAndRemoteChangeHasUpdate; + + [SerializeField] private GitBranch currentBranch = GitBranch.Default; + [SerializeField] private GitRemote currentRemote = GitRemote.Default; + [SerializeField] private List localBranches; [SerializeField] private List remoteBranches; @@ -109,6 +115,17 @@ public override void OnFocusChanged() } } + private void RepositoryOnCurrentBranchAndRemoteChanged(CacheUpdateEvent cacheUpdateEvent) + { + if (!lastCurrentBranchAndRemoteChange.Equals(cacheUpdateEvent)) + { + lastCurrentBranchAndRemoteChange = cacheUpdateEvent; + currentBranchAndRemoteChangeHasUpdate = true; + Redraw(); + } + } + + private void RepositoryOnLocalAndRemoteBranchListChanged(CacheUpdateEvent cacheUpdateEvent) { if (!lastLocalAndRemoteBranchListChangedEvent.Equals(cacheUpdateEvent)) @@ -121,13 +138,25 @@ private void RepositoryOnLocalAndRemoteBranchListChanged(CacheUpdateEvent cacheU private void MaybeUpdateData() { + if (currentBranchAndRemoteChangeHasUpdate) + { + currentBranch = Repository.CurrentBranch ?? GitBranch.Default; + currentRemote = Repository.CurrentRemote ?? GitRemote.Default; + } + if (localAndRemoteBranchListHasUpdate) { - localAndRemoteBranchListHasUpdate = false; localBranches = Repository.LocalBranches.ToList(); remoteBranches = Repository.RemoteBranches.ToList(); + } + + if (currentBranchAndRemoteChangeHasUpdate || localAndRemoteBranchListHasUpdate) + { + currentBranchAndRemoteChangeHasUpdate = false; + localAndRemoteBranchListHasUpdate = false; + BuildTree(); } @@ -143,16 +172,19 @@ public override void OnGUI() private void AttachHandlers(IRepository repository) { repository.LocalAndRemoteBranchListChanged += RepositoryOnLocalAndRemoteBranchListChanged; + repository.CurrentBranchAndRemoteChanged += RepositoryOnCurrentBranchAndRemoteChanged; } private void DetachHandlers(IRepository repository) { repository.LocalAndRemoteBranchListChanged -= RepositoryOnLocalAndRemoteBranchListChanged; + repository.CurrentBranchAndRemoteChanged -= RepositoryOnCurrentBranchAndRemoteChanged; } private void ValidateCachedData(IRepository repository) { repository.CheckAndRaiseEventsIfCacheNewer(CacheType.Branches, lastLocalAndRemoteBranchListChangedEvent); + repository.CheckAndRaiseEventsIfCacheNewer(CacheType.RepositoryInfo, lastCurrentBranchAndRemoteChange); } private void Render() @@ -187,8 +219,8 @@ private void BuildTree() localBranches.Sort(CompareBranches); remoteBranches.Sort(CompareBranches); - treeLocals.Load(localBranches.Select(branch => new GitBranchTreeData(branch))); - treeRemotes.Load(remoteBranches.Select(branch => new GitBranchTreeData(branch))); + treeLocals.Load(localBranches.Select(branch => new GitBranchTreeData(branch, currentBranch != GitBranch.Default && currentBranch.Name == branch.Name))); + treeRemotes.Load(remoteBranches.Select(branch => new GitBranchTreeData(branch, false))); Redraw(); } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/GitPathView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/GitPathView.cs index b2031f83e..a68da21b4 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/GitPathView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/GitPathView.cs @@ -11,11 +11,15 @@ class GitPathView : Subview private const string GitInstallTitle = "Git installation"; private const string PathToGit = "Path to Git"; private const string GitPathSaveButton = "Save Path"; - private const string GitInstallFindButton = "Find install"; + private const string UseInternalGitButton = "Use internal git"; + private const string FindSystemGitButton = "Find system git"; private const string BrowseButton = "..."; private const string GitInstallBrowseTitle = "Select git binary"; private const string ErrorInvalidPathMessage = "Invalid Path."; - private const string ErrorGettingSoftwareVersionMessage = "Error getting software versions."; + private const string ErrorInstallingInternalGit = "Error installing portable git."; + private const string ErrorValidatingGitPath = "Error validating Git Path."; + private const string ErrorGitNotFoundMessage = "Git not found."; + private const string ErrorGitLfsNotFoundMessage = "Git LFS not found."; private const string ErrorMinimumGitVersionMessageFormat = "Git version {0} found. Git version {1} is required."; private const string ErrorMinimumGitLfsVersionMessageFormat = "Git LFS version {0} found. Git LFS version {1} is required."; @@ -31,11 +35,15 @@ class GitPathView : Subview [NonSerialized] private bool isBusy; [NonSerialized] private bool gitExecHasChanged; [NonSerialized] private bool gitExecutableIsSet; + [NonSerialized] private string portableGitPath; public override void InitializeView(IView parent) { base.InitializeView(parent); gitExecutableIsSet = Environment.GitExecutablePath.IsInitialized; + + var gitInstallDetails = new GitInstaller.GitInstallDetails(Environment.UserCachePath, Environment.IsWindows); + portableGitPath = gitInstallDetails.GitExecutablePath; } public override void OnEnable() @@ -110,8 +118,21 @@ public override void OnGUI() } EditorGUI.EndDisabledGroup(); + // disable if we are not on windows + // disable if the newPath == portableGitPath + EditorGUI.BeginDisabledGroup(!Environment.IsWindows || Environment.IsWindows && newGitExec == portableGitPath); + if (GUILayout.Button(UseInternalGitButton, GUILayout.ExpandWidth(false))) + { + GUI.FocusControl(null); + + Logger.Trace("Expected portableGitPath: {0}", portableGitPath); + newGitExec = portableGitPath; + CheckEnteredGitPath(); + } + EditorGUI.EndDisabledGroup(); + //Find button - for attempting to locate a new install - if (GUILayout.Button(GitInstallFindButton, GUILayout.ExpandWidth(false))) + if (GUILayout.Button(FindSystemGitButton, GUILayout.ExpandWidth(false))) { GUI.FocusControl(null); isBusy = true; @@ -120,7 +141,8 @@ public override void OnGUI() CheckEnteredGitPath(); new FindExecTask("git", Manager.CancellationToken) - .Configure(Manager.ProcessManager) + .Configure(Manager.ProcessManager, false, true) + .Catch(ex => true) .FinallyInUI((success, ex, path) => { if (success) { @@ -211,61 +233,108 @@ private void CheckEnteredGitPath() private void ValidateAndSetGitInstallPath(string value) { - //Logger.Trace("Validating Git Path:{0}", value); + value = value.Trim(); - gitVersionErrorMessage = null; + if (value == portableGitPath) + { + Logger.Trace("Attempting to restore portable Git Path:{0}", value); - GitClient.ValidateGitInstall(value.ToNPath()) - .ThenInUI((sucess, result) => - { - if (!sucess) - { - Logger.Trace(ErrorGettingSoftwareVersionMessage); - gitVersionErrorMessage = ErrorGettingSoftwareVersionMessage; - } - else if (!result.IsValid) - { - Logger.Warning( - "Software versions do not meet minimums Git:{0} (Minimum:{1}) GitLfs:{2} (Minimum:{3})", - result.GitVersion, Constants.MinimumGitVersion, result.GitLfsVersion, - Constants.MinimumGitLfsVersion); + var gitInstaller = new GitInstaller(Environment, EntryPoint.ApplicationManager.ProcessManager, + EntryPoint.ApplicationManager.TaskManager); - var errorMessageStringBuilder = new StringBuilder(); + gitInstaller.SetupGitIfNeeded() + .FinallyInUI((success, exception, installationState) => + { + Logger.Trace("Setup Git Using the installer:{0}", success); - if (result.GitVersion < Constants.MinimumGitVersion) + if (!success) { - errorMessageStringBuilder.AppendFormat(ErrorMinimumGitVersionMessageFormat, - result.GitVersion, Constants.MinimumGitVersion); + Logger.Error(exception, ErrorInstallingInternalGit); + gitVersionErrorMessage = ErrorValidatingGitPath; + } + else + { + Manager.SystemSettings.Unset(Constants.GitInstallPathKey); + Environment.GitExecutablePath = installationState.GitExecutablePath; + Environment.GitLfsExecutablePath = installationState.GitLfsExecutablePath; + Environment.IsCustomGitExecutable = false; + + gitExecHasChanged = true; } - if (result.GitLfsVersion < Constants.MinimumGitLfsVersion) + isBusy = false; + }).Start(); + } + else + { + //Logger.Trace("Validating Git Path:{0}", value); + + gitVersionErrorMessage = null; + + GitClient.ValidateGitInstall(value.ToNPath(), true) + .ThenInUI((success, result) => + { + if (!success) { - if (errorMessageStringBuilder.Length > 0) + Logger.Trace(ErrorValidatingGitPath); + gitVersionErrorMessage = ErrorValidatingGitPath; + } + else if (!result.IsValid) + { + Logger.Warning( + "Software versions do not meet minimums Git:{0} (Minimum:{1}) GitLfs:{2} (Minimum:{3})", + result.GitVersion, Constants.MinimumGitVersion, result.GitLfsVersion, + Constants.MinimumGitLfsVersion); + + var errorMessageStringBuilder = new StringBuilder(); + + if (result.GitVersion == null) + { + errorMessageStringBuilder.Append(ErrorGitNotFoundMessage); + } + else if (result.GitLfsVersion == null) { - errorMessageStringBuilder.Append(Environment.NewLine); + errorMessageStringBuilder.Append(ErrorGitLfsNotFoundMessage); } + else + { + if (result.GitVersion < Constants.MinimumGitVersion) + { + errorMessageStringBuilder.AppendFormat(ErrorMinimumGitVersionMessageFormat, + result.GitVersion, Constants.MinimumGitVersion); + } - errorMessageStringBuilder.AppendFormat(ErrorMinimumGitLfsVersionMessageFormat, - result.GitLfsVersion, Constants.MinimumGitLfsVersion); - } + if (result.GitLfsVersion < Constants.MinimumGitLfsVersion) + { + if (errorMessageStringBuilder.Length > 0) + { + errorMessageStringBuilder.Append(Environment.NewLine); + } - gitVersionErrorMessage = errorMessageStringBuilder.ToString(); - } - else - { - Logger.Warning("Software versions meet minimums Git:{0} GitLfs:{1}", - result.GitVersion, - result.GitLfsVersion); + errorMessageStringBuilder.AppendFormat(ErrorMinimumGitLfsVersionMessageFormat, + result.GitLfsVersion, Constants.MinimumGitLfsVersion); + } + } - Manager.SystemSettings.Set(Constants.GitInstallPathKey, value); - Environment.GitExecutablePath = value.ToNPath(); + gitVersionErrorMessage = errorMessageStringBuilder.ToString(); + } + else + { + Logger.Trace("Software versions meet minimums Git:{0} GitLfs:{1}", + result.GitVersion, + result.GitLfsVersion); - gitExecHasChanged = true; - } + Manager.SystemSettings.Set(Constants.GitInstallPathKey, value); + Environment.GitExecutablePath = value.ToNPath(); + Environment.IsCustomGitExecutable = true; - isBusy = false; + gitExecHasChanged = true; + } - }).Start(); + isBusy = false; + + }).Start(); + } } public override bool IsBusy diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs index bfa643fb6..2f421b7a6 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs @@ -120,7 +120,7 @@ private void Open(PopupViewType popupViewType, Action onClose) { //Logger.Trace("Validating to open view"); - Client.ValidateCurrentUser(() => { + Client.GetCurrentUser(user => { //Logger.Trace("User validated opening view"); @@ -192,7 +192,7 @@ public IApiClient Client host = UriString.ToUriString(HostAddress.GitHubDotComHostAddress.WebUri); } - client = ApiClient.Create(host, Platform.Keychain, Manager.ProcessManager, TaskManager, Environment.NodeJsExecutablePath, Environment.OctorunScriptPath); + client = new ApiClient(host, Platform.Keychain, Manager.ProcessManager, TaskManager, Environment.NodeJsExecutablePath, Environment.OctorunScriptPath); } return client; diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs index 5cb940fb9..c342f93b3 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ProjectWindowInterface.cs @@ -34,9 +34,8 @@ public static void Initialize(IRepository repo) if (repository != null) { - repository.TrackingStatusChanged += RepositoryOnStatusChanged; + repository.StatusEntriesChanged += RepositoryOnStatusEntriesChanged; repository.LocksChanged += RepositoryOnLocksChanged; - } } @@ -46,7 +45,7 @@ private static void ValidateCachedData(IRepository repository) repository.CheckAndRaiseEventsIfCacheNewer(CacheType.GitLocks, lastLocksChangedEvent); } - private static void RepositoryOnStatusChanged(CacheUpdateEvent cacheUpdateEvent) + private static void RepositoryOnStatusEntriesChanged(CacheUpdateEvent cacheUpdateEvent) { if (!lastRepositoryStatusChangedEvent.Equals(cacheUpdateEvent)) { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs index b62d1e823..077f54266 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs @@ -52,7 +52,7 @@ public IApiClient Client host = UriString.ToUriString(HostAddress.GitHubDotComHostAddress.WebUri); } - client = ApiClient.Create(host, Platform.Keychain, Manager.ProcessManager, TaskManager, Environment.NodeJsExecutablePath, Environment.OctorunScriptPath); + client = new ApiClient(host, Platform.Keychain, Manager.ProcessManager, TaskManager, Environment.NodeJsExecutablePath, Environment.OctorunScriptPath); } return client; diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs index 46e122ba5..bddedb97b 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs @@ -500,7 +500,7 @@ private void SignOut(object obj) host = UriString.ToUriString(HostAddress.GitHubDotComHostAddress.WebUri); } - var apiClient = ApiClient.Create(host, Platform.Keychain, null, null, NPath.Default, NPath.Default); + var apiClient = new ApiClient(host, Platform.Keychain, null, null, NPath.Default, NPath.Default); apiClient.Logout(host); } diff --git a/src/tests/IntegrationTests/BaseIntegrationTest.cs b/src/tests/IntegrationTests/BaseIntegrationTest.cs index d6fad1bc0..8e41ca6b6 100644 --- a/src/tests/IntegrationTests/BaseIntegrationTest.cs +++ b/src/tests/IntegrationTests/BaseIntegrationTest.cs @@ -8,6 +8,7 @@ using System.Linq; using System.IO; using System.Runtime.CompilerServices; +using System.Threading.Tasks; namespace IntegrationTests { @@ -80,11 +81,14 @@ protected void InitializeEnvironment(NPath repoPath, initializeRepository); } - private void InitializePlatform(NPath repoPath, NPath? environmentPath, bool enableEnvironmentTrace, - bool setupGit = true, string testName = "") + protected void InitializePlatform(NPath repoPath, NPath? environmentPath = null, + bool enableEnvironmentTrace = true, + bool setupGit = true, + string testName = "", + bool initializeRepository = true) { InitializeTaskManager(); - InitializeEnvironment(repoPath, environmentPath, enableEnvironmentTrace, true); + InitializeEnvironment(repoPath, environmentPath, enableEnvironmentTrace, initializeRepository); Platform = new Platform(Environment); ProcessManager = new ProcessManager(Environment, GitEnvironment, TaskManager.Token); @@ -141,7 +145,7 @@ protected void SetupGit(NPath pathToSetupGitInto, string testName) { var autoResetEvent = new AutoResetEvent(false); - var installDetails = new GitInstaller.GitInstallDetails(pathToSetupGitInto, true); + var installDetails = new GitInstaller.GitInstallDetails(pathToSetupGitInto, Environment.IsWindows); var zipArchivesPath = pathToSetupGitInto.Combine("downloads").CreateDirectory(); @@ -157,16 +161,13 @@ protected void SetupGit(NPath pathToSetupGitInto, string testName) NPath? result = null; Exception ex = null; - var setupTask = gitInstaller.SetupGitIfNeeded(); - setupTask.OnEnd += (thisTask, _, __, ___) => { - ((ITask)thisTask.GetEndOfChain()).OnEnd += (t, path, success, exception) => + var setupTask = gitInstaller.SetupGitIfNeeded().Finally((success, state) => { - result = path; - ex = exception; + result = state.GitExecutablePath; autoResetEvent.Set(); - }; - }; + }); setupTask.Start(); + if (!autoResetEvent.WaitOne(TimeSpan.FromMinutes(5))) throw new TimeoutException($"Test setup unzipping {zipArchivesPath} to {pathToSetupGitInto} timed out"); @@ -216,6 +217,13 @@ public virtual void OnTearDown() { TaskManager.Dispose(); Environment?.CacheContainer.Dispose(); + BranchesCache.Instance = null; + GitAheadBehindCache.Instance = null; + GitLocksCache.Instance = null; + GitLogCache.Instance = null; + GitStatusCache.Instance = null; + GitUserCache.Instance = null; + RepositoryInfoCache.Instance = null; Logger.Debug("Deleting TestBasePath: {0}", TestBasePath.ToString()); for (var i = 0; i < 5; i++) diff --git a/src/tests/IntegrationTests/CachingClasses.cs b/src/tests/IntegrationTests/CachingClasses.cs index 685a21cd9..2f6e89353 100644 --- a/src/tests/IntegrationTests/CachingClasses.cs +++ b/src/tests/IntegrationTests/CachingClasses.cs @@ -19,6 +19,7 @@ public static T Instance CreateAndLoad(); return instance; } + set { instance = value; } } protected ScriptObjectSingleton() @@ -449,172 +450,29 @@ sealed class BranchesCache : ManagedCacheBase, IBranchCache public BranchesCache() : base(CacheType.Branches) { } - - public GitBranch[] LocalBranches - { - get { return localBranches; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} localBranches:{1}", now, value); - - var localBranchesIsNull = localBranches == null; - var valueIsNull = value == null; - - if (localBranchesIsNull != valueIsNull || - !localBranchesIsNull && !localBranches.SequenceEqual(value)) - { - localBranches = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public GitBranch[] RemoteBranches - { - get { return remoteBranches; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} remoteBranches:{1}", now, value); - - var remoteBranchesIsNull = remoteBranches == null; - var valueIsNull = value == null; - - if (remoteBranchesIsNull != valueIsNull || - !remoteBranchesIsNull && !remoteBranches.SequenceEqual(value)) - { - remoteBranches = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public GitRemote[] Remotes - { - get { return remotes; } - set - { - var now = DateTimeOffset.Now; - var isUpdated = false; - - Logger.Trace("Updating: {0} remotes:{1}", now, value); - - var remotesIsNull = remotes == null; - var valueIsNull = value == null; - - if (remotesIsNull != valueIsNull || - !remotesIsNull && !remotes.SequenceEqual(value)) - { - remotes = value; - isUpdated = true; - } - - SaveData(now, isUpdated); - } - } - - public void RemoveLocalBranch(string branch) - { - if (LocalConfigBranches.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - LocalConfigBranches.Remove(branch); - Logger.Trace("RemoveLocalBranch {0} branch:{1} ", now, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is not found", branch); - } - } - - public void AddLocalBranch(string branch) - { - if (!LocalConfigBranches.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - LocalConfigBranches.Add(branch, new ConfigBranch(branch)); - Logger.Trace("AddLocalBranch {0} branch:{1} ", now, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is already present", branch); - } - } - - public void AddRemoteBranch(string remote, string branch) - { - Dictionary branchList; - if (RemoteConfigBranches.TryGetValue(remote, out branchList)) - { - if (!branchList.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - branchList.Add(branch, new ConfigBranch(branch, ConfigRemotes[remote])); - Logger.Trace("AddRemoteBranch {0} remote:{1} branch:{2} ", now, remote, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is already present in Remote {1}", branch, remote); - } - } - else - { - Logger.Warning("Remote {0} is not found", remote); - } - } - - public void RemoveRemoteBranch(string remote, string branch) - { - Dictionary branchList; - if (RemoteConfigBranches.TryGetValue(remote, out branchList)) - { - if (branchList.ContainsKey(branch)) - { - var now = DateTimeOffset.Now; - branchList.Remove(branch); - Logger.Trace("RemoveRemoteBranch {0} remote:{1} branch:{2} ", now, remote, branch); - SaveData(now, true); - } - else - { - Logger.Warning("Branch {0} is not found in Remote {1}", branch, remote); - } - } - else - { - Logger.Warning("Remote {0} is not found", remote); - } - } - - public void SetRemotes(Dictionary remoteDictionary, Dictionary> branchDictionary) + public void SetRemotes(Dictionary remoteConfigs, Dictionary> configBranches, GitRemote[] gitRemotes, GitBranch[] gitBranches) { var now = DateTimeOffset.Now; - configRemotes = new ConfigRemoteDictionary(remoteDictionary); - remoteConfigBranches = new RemoteConfigBranchDictionary(branchDictionary); + configRemotes = new ConfigRemoteDictionary(remoteConfigs); + remoteConfigBranches = new RemoteConfigBranchDictionary(remoteConfigBranches); + remotes = gitRemotes; + remoteBranches = gitBranches; Logger.Trace("SetRemotes {0}", now); SaveData(now, true); } - public void SetLocals(Dictionary branchDictionary) + public void SetLocals(Dictionary configBranches, GitBranch[] gitBranches) { var now = DateTimeOffset.Now; - localConfigBranches = new LocalConfigBranchDictionary(branchDictionary); - Logger.Trace("SetRemotes {0}", now); + localConfigBranches = new LocalConfigBranchDictionary(configBranches); + localBranches = gitBranches; + Logger.Trace("SetLocals {0}", now); SaveData(now, true); } + public GitBranch[] LocalBranches { get { return localBranches; } } + public GitBranch[] RemoteBranches { get { return remoteBranches; } } + public GitRemote[] Remotes { get { return remotes; } } public ILocalConfigBranchDictionary LocalConfigBranches { get { return localConfigBranches; } } public IRemoteConfigBranchDictionary RemoteConfigBranches { get { return remoteConfigBranches; } } public IConfigRemoteDictionary ConfigRemotes { get { return configRemotes; } } @@ -640,7 +498,7 @@ public List Log var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} gitLog:{1}", now, value); + Logger.Trace("{0} Updating Log: current:{1} new:{2}", now, log.Count, value.Count); if (!log.SequenceEqual(value)) { @@ -675,7 +533,7 @@ public int Ahead var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} ahead:{1}", now, value); + Logger.Trace("{0} Updating Ahead: current:{1} new:{2}", now, ahead, value); if (ahead != value) { @@ -699,7 +557,7 @@ public int Behind var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} behind:{1}", now, value); + Logger.Trace("{0} Updating Behind: current:{1} new:{2}", now, behind, value); if (behind != value) { @@ -733,7 +591,7 @@ public List Entries var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} entries:{1}", now, value.Count); + Logger.Trace("{0} Updating Entries: current:{1} new:{2}", now, entries.Count, value.Count); if (!entries.SequenceEqual(value)) { @@ -767,7 +625,7 @@ public List GitLocks var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} gitLocks:{1}", now, value); + Logger.Trace("{0} Updating GitLocks: current:{1} new:{2}", now, gitLocks.Count, value.Count); if (!gitLocks.SequenceEqual(value)) { @@ -802,7 +660,7 @@ public string Name var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} Name:{1}", now, value); + Logger.Trace("{0} Updating Name: current:{1} new:{2}", now, gitName, value); if (gitName != value) { @@ -826,7 +684,7 @@ public string Email var now = DateTimeOffset.Now; var isUpdated = false; - Logger.Trace("Updating: {0} Email:{1}", now, value); + Logger.Trace("{0} Updating Email: current:{1} new:{2}", now, gitEmail, value); if (gitEmail != value) { diff --git a/src/tests/IntegrationTests/Download/DownloadTaskTests.cs b/src/tests/IntegrationTests/Download/DownloadTaskTests.cs index 11719fc0a..ae20aba5d 100644 --- a/src/tests/IntegrationTests/Download/DownloadTaskTests.cs +++ b/src/tests/IntegrationTests/Download/DownloadTaskTests.cs @@ -21,8 +21,8 @@ public async Task DownloadAndVerificationWorks() ILogging logger; StartTest(out watch, out logger); - var fileUrl = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - var md5Url = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var fileUrl = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + var md5Url = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloader = new Downloader(); StartTrackTime(watch, logger, md5Url); @@ -45,7 +45,7 @@ public async Task DownloadingNonExistingFileThrows() StartTest(out watch, out logger); var fileUrl = new UriString($"http://localhost:{server.Port}/nope"); - var md5Url = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var md5Url = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloader = new Downloader(); StartTrackTime(watch, logger, md5Url); @@ -63,8 +63,8 @@ public async Task FailsIfVerificationFails() ILogging logger; StartTest(out watch, out logger); - var fileUrl = new UriString($"http://localhost:{server.Port}/git.zip"); - var md5Url = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var fileUrl = new UriString($"http://localhost:{server.Port}/git/windows/git.zip"); + var md5Url = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloader = new Downloader(); StartTrackTime(watch, logger, md5Url); @@ -83,8 +83,8 @@ public async Task ResumingWorks() StartTest(out watch, out logger); var fileSystem = NPath.FileSystem; - var fileUrl = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - var md5Url = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var fileUrl = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + var md5Url = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloader = new Downloader(); StartTrackTime(watch, logger, md5Url); @@ -124,8 +124,8 @@ public async Task SucceedIfEverythingIsAlreadyDownloaded() StartTest(out watch, out logger); var fileSystem = NPath.FileSystem; - var fileUrl = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - var md5Url = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var fileUrl = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + var md5Url = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloader = new Downloader(); StartTrackTime(watch, logger, md5Url); @@ -157,10 +157,10 @@ public async Task DownloadsRunSideBySide() ILogging logger; StartTest(out watch, out logger); - var fileUrl1 = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - var md5Url1 = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); - var fileUrl2 = new UriString($"http://localhost:{server.Port}/git.zip"); - var md5Url2 = new UriString($"http://localhost:{server.Port}/git.zip.md5"); + var fileUrl1 = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + var md5Url1 = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); + var fileUrl2 = new UriString($"http://localhost:{server.Port}/git/windows/git.zip"); + var md5Url2 = new UriString($"http://localhost:{server.Port}/git/windows/git.zip.md5"); var events = new List(); @@ -200,8 +200,8 @@ public async Task ResumingDownloadsWorks() var fileSystem = NPath.FileSystem; - var gitLfs = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + var gitLfs = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); var downloadTask = new DownloadTask(TaskManager.Token, fileSystem, gitLfsMd5, TestBasePath); @@ -290,7 +290,7 @@ public void DownloadingNonExistingFileThrows() // var fileSystem = NPath.FileSystem; - // var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + // var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); // var downloadTask = new DownloadTextTask(TaskManager.Token, fileSystem, gitLfsMd5, TestBasePath); @@ -348,8 +348,8 @@ public void DownloadingFromNonExistingDomainThrows() // var fileSystem = NPath.FileSystem; - // var gitLfs = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); - // var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git-lfs.zip.md5"); + // var gitLfs = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); + // var gitLfsMd5 = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip.md5"); // var downloadGitLfsMd5Task = new DownloadTextTask(TaskManager.Token, fileSystem, gitLfsMd5, TestBasePath); // var downloadGitLfsTask = new DownloadTask(TaskManager.Token, fileSystem, gitLfs, TestBasePath); @@ -391,7 +391,7 @@ public void ShutdownTimeWhenTaskManagerDisposed() var evtFinally = new AutoResetEvent(false); Exception exception = null; - var gitLfs = new UriString($"http://localhost:{server.Port}/git-lfs.zip"); + var gitLfs = new UriString($"http://localhost:{server.Port}/git/windows/git-lfs.zip"); StartTrackTime(watch, logger, gitLfs); var downloadGitTask = new DownloadTask(TaskManager.Token, fileSystem, gitLfs, TestBasePath) diff --git a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs index a7a4a0c22..d5452f621 100644 --- a/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs +++ b/src/tests/IntegrationTests/Events/RepositoryManagerTests.cs @@ -18,7 +18,7 @@ namespace IntegrationTests class RepositoryManagerTests : BaseGitEnvironmentTest { private RepositoryManagerEvents repositoryManagerEvents; - private TimeSpan Timeout = TimeSpan.FromSeconds(5); + private TimeSpan Timeout = TimeSpan.FromMilliseconds(1200); public override void OnSetup() { @@ -80,8 +80,6 @@ public async Task ShouldDetectFileChanges() repositoryManagerListener.AttachListener(manager, repositoryManagerEvents); }); - //repositoryManagerListener.ClearReceivedCalls(); - //repositoryManagerEvents.Reset(); repositoryManagerListener.AssertDidNotReceiveAnyCalls(); var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); @@ -94,19 +92,19 @@ public async Task ShouldDetectFileChanges() StopTrackTimeAndLog(watch, logger); StartTrackTime(watch, logger, "repositoryManagerEvents.WaitForNotBusy()"); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); StopTrackTimeAndLog(watch, logger); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.DidNotReceive().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.DidNotReceive().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.CurrentBranchUpdated); } finally { @@ -135,48 +133,50 @@ public async Task ShouldAddAndCommitFiles() var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); foobarTxt.WriteAllText("foobar"); - var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt"); - testDocumentTxt.WriteAllText("foobar"); - - await TaskManager.Wait(); - + StartTrackTime(watch, logger, "RepositoryManager.WaitForEvents()"); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); + StopTrackTimeAndLog(watch, logger); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); + StartTrackTime(watch, logger, "repositoryManagerEvents.WaitForNotBusy()"); + await repositoryManagerEvents.WaitForNotBusy(); + StopTrackTimeAndLog(watch, logger); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.DidNotReceive().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.DidNotReceive().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.CurrentBranchUpdated); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); - await RepositoryManager - .CommitFiles(new List { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty) - .StartAsAsync(); + var filesToCommit = new List { "foobar.txt" }; + var commitMessage = "IntegrationTest Commit"; + var commitBody = string.Empty; + + StartTrackTime(watch, logger, "CommitFiles"); + await RepositoryManager.CommitFiles(filesToCommit, commitMessage, commitBody).StartAsAsync(); + StopTrackTimeAndLog(watch, logger); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -184,6 +184,16 @@ await RepositoryManager } } + private async Task AssertReceivedEvent(Task task) + { + (await TaskEx.WhenAny(task, TaskEx.Delay(Timeout))).Should().BeAssignableTo>("otherwise the event was not raised"); + } + + private async Task AssertDidNotReceiveEvent(Task task) + { + (await TaskEx.WhenAny(task, TaskEx.Delay(Timeout))).Should().BeAssignableTo>("otherwise the event was raised"); + } + [Test] public async Task ShouldAddAndCommitAllFiles() { @@ -202,12 +212,11 @@ public async Task ShouldAddAndCommitAllFiles() repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + logger.Trace("Add files"); + var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt"); foobarTxt.WriteAllText("foobar"); - var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt"); - testDocumentTxt.WriteAllText("foobar"); - await TaskManager.Wait(); StartTrackTime(watch, logger, "RepositoryManager.WaitForEvents()"); @@ -215,27 +224,25 @@ public async Task ShouldAddAndCommitAllFiles() StopTrackTimeAndLog(watch, logger); StartTrackTime(watch, logger, "repositoryManagerEvents.WaitForNotBusy()"); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); StopTrackTimeAndLog(watch, logger); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.DidNotReceive().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.DidNotReceive().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.CurrentBranchUpdated); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); StartTrackTime(watch, logger, "CommitAllFiles"); - await RepositoryManager - .CommitAllFiles("IntegrationTest Commit", string.Empty) - .StartAsAsync(); + await RepositoryManager.CommitAllFiles("IntegrationTest Commit", string.Empty).StartAsAsync(); StopTrackTimeAndLog(watch, logger); await TaskManager.Wait(); @@ -245,21 +252,19 @@ await RepositoryManager StopTrackTimeAndLog(watch, logger); StartTrackTime(watch, logger, "repositoryManagerEvents.WaitForNotBusy()"); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); StopTrackTimeAndLog(watch, logger); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -289,20 +294,18 @@ public async Task ShouldDetectBranchChange() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.DidNotReceive().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.DidNotReceive().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -332,22 +335,20 @@ public async Task ShouldDetectBranchDelete() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.RemoteBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitAheadBehindStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); + await TaskEx.Delay(Timeout); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -373,23 +374,34 @@ public async Task ShouldDetectBranchCreate() repositoryManagerListener.AssertDidNotReceiveAnyCalls(); + { + // prepopulate repository info cache + var b = Repository.CurrentBranch; + await TaskManager.Wait(); + RepositoryManager.WaitForEvents(); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + repositoryManagerListener.ClearReceivedCalls(); + repositoryManagerEvents.Reset(); + } + var createdBranch1 = "feature/document2"; await RepositoryManager.CreateBranch(createdBranch1, "feature/document").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.DidNotReceive().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + // TODO: log should not be getting called, but it is because when branches get changed we're blindly calling log + //await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.CurrentBranchUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); @@ -401,18 +413,19 @@ public async Task ShouldDetectBranchCreate() RepositoryManager.WaitForEvents(); StopTrackTimeAndLog(watch, logger); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.DidNotReceive().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + // TODO: log should not be getting called, but it is because when branches get changed we're blindly calling log + //await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.CurrentBranchUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -442,22 +455,18 @@ public async Task ShouldDetectChangesToRemotes() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.RemoteBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitAheadBehindStatusUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); @@ -466,22 +475,18 @@ public async Task ShouldDetectChangesToRemotes() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.RemoteBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitAheadBehindStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -507,49 +512,42 @@ public async Task ShouldDetectChangesToRemotesWhenSwitchingBranches() repositoryManagerListener.AssertDidNotReceiveAnyCalls(); - await RepositoryManager.CreateBranch("branch2", "another/master") - .StartAsAsync(); + await RepositoryManager.CreateBranch("branch2", "another/master").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); + await repositoryManagerEvents.WaitForNotBusy(); - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.RemoteBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitAheadBehindStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); repositoryManagerListener.ClearReceivedCalls(); repositoryManagerEvents.Reset(); - await RepositoryManager.SwitchBranch("branch2") - .StartAsAsync(); + await RepositoryManager.SwitchBranch("branch2").StartAsAsync(); await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.CurrentBranchUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitLogUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.DidNotReceive().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -579,19 +577,19 @@ public async Task ShouldDetectGitPull() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.LocalBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.Received().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.Received().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.DidNotReceive().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.GitStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitLogUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + // TODO: this should not happen but it's happening right now because when local branches get updated in the cache, remotes get updated too + //await AssertDidNotReceiveEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { @@ -621,19 +619,20 @@ public async Task ShouldDetectGitFetch() await TaskManager.Wait(); RepositoryManager.WaitForEvents(); - repositoryManagerEvents.WaitForNotBusy(); - - repositoryManagerEvents.RemoteBranchesUpdated.WaitOne(Timeout).Should().BeTrue(); - repositoryManagerEvents.GitAheadBehindStatusUpdated.WaitOne(Timeout).Should().BeTrue(); - - repositoryManagerListener.Received().OnIsBusyChanged(Args.Bool); - repositoryManagerListener.Received().CurrentBranchUpdated(Args.NullableConfigBranch, Args.NullableConfigRemote); - repositoryManagerListener.Received().GitAheadBehindStatusUpdated(Args.GitAheadBehindStatus); - repositoryManagerListener.DidNotReceive().GitStatusUpdated(Args.GitStatus); - repositoryManagerListener.DidNotReceive().GitLocksUpdated(Args.GitLocks); - repositoryManagerListener.DidNotReceive().GitLogUpdated(Args.GitLogs); - repositoryManagerListener.Received().LocalBranchesUpdated(Args.LocalBranchDictionary); - repositoryManagerListener.Received().RemoteBranchesUpdated(Args.RemoteDictionary, Args.RemoteBranchDictionary); + await repositoryManagerEvents.WaitForNotBusy(); + + // we expect these events + await AssertReceivedEvent(repositoryManagerEvents.LocalBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.RemoteBranchesUpdated); + await AssertReceivedEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertReceivedEvent(repositoryManagerEvents.CurrentBranchUpdated); + + // we don't expect these events + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitStatusUpdated); + // TODO: log should not be getting called, but it is because when branches get changed we're blindly calling log + //await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLogUpdated); + //await AssertDidNotReceiveEvent(repositoryManagerEvents.GitAheadBehindStatusUpdated); + await AssertDidNotReceiveEvent(repositoryManagerEvents.GitLocksUpdated); } finally { diff --git a/src/tests/IntegrationTests/Installer/GitInstallerTests.cs b/src/tests/IntegrationTests/Installer/GitInstallerTests.cs index c77d55e98..05bedd749 100644 --- a/src/tests/IntegrationTests/Installer/GitInstallerTests.cs +++ b/src/tests/IntegrationTests/Installer/GitInstallerTests.cs @@ -15,7 +15,7 @@ class GitInstallerTests : BaseIntegrationTest public override void OnSetup() { base.OnSetup(); - InitializeEnvironment(TestBasePath, initializeRepository: false); + InitializePlatform(TestBasePath, setupGit: false, initializeRepository: false); } private TestWebServer.HttpServer server; @@ -32,10 +32,11 @@ public override void TestFixtureTearDown() base.TestFixtureTearDown(); server.Stop(); ApplicationConfiguration.WebTimeout = ApplicationConfiguration.DefaultWebTimeout; + ZipHelper.Instance = null; } [Test] - public void GitInstallTest() + public void GitInstallWindows() { var gitInstallationPath = TestBasePath.Combine("GitInstall").CreateDirectory(); @@ -49,14 +50,84 @@ public void GitInstallTest() TestBasePath.Combine("git").CreateDirectory(); + var zipHelper = Substitute.For(); + zipHelper.Extract(Arg.Any(), Arg.Do(x => + { + var n = x.ToNPath(); + n.EnsureDirectoryExists(); + if (n.FileName == "git-lfs") + { + n.Combine("git-lfs" + Environment.ExecutableExtension).WriteAllText(""); + } + }), Arg.Any(), Arg.Any>()).Returns(true); + ZipHelper.Instance = zipHelper; + var gitInstaller = new GitInstaller(Environment, ProcessManager, TaskManager, installDetails); + + TaskCompletionSource end = new TaskCompletionSource(); + var startTask = gitInstaller.SetupGitIfNeeded().Finally((_, state) => end.TrySetResult(state)); + startTask.Start(); + GitInstaller.GitInstallationState result = null; + Assert.DoesNotThrow(async () => result = await end.Task); + result.Should().NotBeNull(); + + Assert.AreEqual(gitInstallationPath.Combine(installDetails.PackageNameWithVersion), result.GitInstallationPath); + result.GitExecutablePath.Should().Be(gitInstallationPath.Combine(installDetails.PackageNameWithVersion, "cmd", "git" + Environment.ExecutableExtension)); + result.GitLfsExecutablePath.Should().Be(gitInstallationPath.Combine(installDetails.PackageNameWithVersion, "mingw32", "libexec", "git-core", "git-lfs" + Environment.ExecutableExtension)); + + var isCustomGitExec = result.GitExecutablePath != result.GitExecutablePath; + + Environment.GitExecutablePath = result.GitExecutablePath; + Environment.GitLfsExecutablePath = result.GitLfsExecutablePath; + + Environment.IsCustomGitExecutable = isCustomGitExec; + + var procTask = new SimpleProcessTask(TaskManager.Token, "something") + .Configure(ProcessManager); + procTask.Process.StartInfo.EnvironmentVariables["PATH"].Should().StartWith(gitInstallationPath.ToString()); + } + + //[Test] + public void GitInstallMac() + { + var filesystem = Substitute.For(); + DefaultEnvironment.OnMac = true; + DefaultEnvironment.OnWindows = false; + + var gitInstallationPath = TestBasePath.Combine("GitInstall").CreateDirectory(); + + var installDetails = new GitInstaller.GitInstallDetails(gitInstallationPath, Environment.IsWindows) + { + GitZipMd5Url = $"http://localhost:{server.Port}/{new Uri(GitInstaller.GitInstallDetails.DefaultGitZipMd5Url).AbsolutePath}", + GitZipUrl = $"http://localhost:{server.Port}/{new Uri(GitInstaller.GitInstallDetails.DefaultGitZipUrl).AbsolutePath}", + GitLfsZipMd5Url = $"http://localhost:{server.Port}/{new Uri(GitInstaller.GitInstallDetails.DefaultGitLfsZipMd5Url).AbsolutePath}", + GitLfsZipUrl = $"http://localhost:{server.Port}/{new Uri(GitInstaller.GitInstallDetails.DefaultGitLfsZipUrl).AbsolutePath}", + }; + + TestBasePath.Combine("git").CreateDirectory(); + var gitInstaller = new GitInstaller(Environment, ProcessManager, TaskManager, installDetails); var startTask = gitInstaller.SetupGitIfNeeded(); - var endTask = new FuncTask(TaskManager.Token, (s, path) => path); + var endTask = new FuncTask(TaskManager.Token, (s, state) => state); startTask.OnEnd += (thisTask, path, success, exception) => thisTask.GetEndOfChain().Then(endTask); startTask.Start(); - NPath? resultPath = null; - Assert.DoesNotThrow(async () => resultPath = await endTask.Task); - resultPath.Should().NotBeNull(); + GitInstaller.GitInstallationState result = null; + Assert.DoesNotThrow(async () => result = await endTask.Task); + result.Should().NotBeNull(); + + Assert.AreEqual(gitInstallationPath.Combine(installDetails.PackageNameWithVersion), result.GitInstallationPath); + result.GitExecutablePath.Should().Be(gitInstallationPath.Combine("bin", "git" + Environment.ExecutableExtension)); + result.GitLfsExecutablePath.Should().Be(gitInstallationPath.Combine(installDetails.PackageNameWithVersion, "libexec", "git-core", "git-lfs" + Environment.ExecutableExtension)); + + var isCustomGitExec = result.GitExecutablePath != result.GitExecutablePath; + + Environment.GitExecutablePath = result.GitExecutablePath; + Environment.GitLfsExecutablePath = result.GitLfsExecutablePath; + + Environment.IsCustomGitExecutable = isCustomGitExec; + + var procTask = new SimpleProcessTask(TaskManager.Token, "something") + .Configure(ProcessManager); + procTask.Process.StartInfo.EnvironmentVariables["PATH"].Should().StartWith(gitInstallationPath.ToString()); } } } \ No newline at end of file diff --git a/src/tests/IntegrationTests/IntegrationTestEnvironment.cs b/src/tests/IntegrationTests/IntegrationTestEnvironment.cs index 113346da9..06bc6f744 100644 --- a/src/tests/IntegrationTests/IntegrationTestEnvironment.cs +++ b/src/tests/IntegrationTests/IntegrationTestEnvironment.cs @@ -84,10 +84,13 @@ public string GetSpecialFolder(Environment.SpecialFolder folder) public string UserProfilePath => UserCachePath.Parent.CreateDirectory("user profile path"); - public NPath Path => Environment.GetEnvironmentVariable("PATH").ToNPath(); + public string Path { get; set; } = Environment.GetEnvironmentVariable("PATH").ToNPath(); + public string NewLine => Environment.NewLine; public string UnityVersion => "5.6"; + public bool IsCustomGitExecutable { get; set; } + public NPath GitExecutablePath { get { return defaultEnvironment.GitExecutablePath; } @@ -126,6 +129,8 @@ public NPath GitExecutablePath public NPath RepositoryPath => defaultEnvironment.RepositoryPath; public NPath GitInstallPath => defaultEnvironment.GitInstallPath; + public NPath GitLfsInstallPath => defaultEnvironment.GitLfsInstallPath; + public NPath GitLfsExecutablePath { get { return defaultEnvironment.GitLfsExecutablePath; } set { defaultEnvironment.GitLfsExecutablePath = value; } } public IRepository Repository { get { return defaultEnvironment.Repository; } set { defaultEnvironment.Repository = value; } } public IUser User { get { return defaultEnvironment.User; } set { defaultEnvironment.User = value; } } diff --git a/src/tests/IntegrationTests/Process/ProcessManagerIntegrationTests.cs b/src/tests/IntegrationTests/Process/ProcessManagerIntegrationTests.cs index f843fe7be..bdd418605 100644 --- a/src/tests/IntegrationTests/Process/ProcessManagerIntegrationTests.cs +++ b/src/tests/IntegrationTests/Process/ProcessManagerIntegrationTests.cs @@ -23,8 +23,8 @@ public async Task BranchListTest() .StartAsAsync(); gitBranches.Should().BeEquivalentTo( - new GitBranch("master", "origin/master: behind 1", true), - new GitBranch("feature/document", "origin/feature/document", false)); + new GitBranch("master", "origin/master: behind 1"), + new GitBranch("feature/document", "origin/feature/document")); } [Test] diff --git a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs index ec0c4ddce..240996b82 100644 --- a/src/tests/TestUtils/Events/IRepositoryManagerListener.cs +++ b/src/tests/TestUtils/Events/IRepositoryManagerListener.cs @@ -6,6 +6,7 @@ using NSubstitute; using GitHub.Logging; using NUnit.Framework; +using System.Threading.Tasks; namespace TestUtils.Events { @@ -23,33 +24,48 @@ interface IRepositoryManagerListener class RepositoryManagerEvents { - public EventWaitHandle IsBusy { get; } = new AutoResetEvent(false); - public EventWaitHandle IsNotBusy { get; } = new AutoResetEvent(false); - public EventWaitHandle CurrentBranchUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle GitAheadBehindStatusUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle GitStatusUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle GitLocksUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle GitLogUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle LocalBranchesUpdated { get; } = new AutoResetEvent(false); - public EventWaitHandle RemoteBranchesUpdated { get; } = new AutoResetEvent(false); + internal TaskCompletionSource isBusy; + public Task IsBusy => isBusy.Task; + internal TaskCompletionSource isNotBusy; + public Task IsNotBusy => isNotBusy.Task; + internal TaskCompletionSource currentBranchUpdated; + public Task CurrentBranchUpdated => currentBranchUpdated.Task; + internal TaskCompletionSource gitAheadBehindStatusUpdated; + public Task GitAheadBehindStatusUpdated => gitAheadBehindStatusUpdated.Task; + internal TaskCompletionSource gitStatusUpdated; + public Task GitStatusUpdated => gitStatusUpdated.Task; + internal TaskCompletionSource gitLocksUpdated; + public Task GitLocksUpdated => gitLocksUpdated.Task; + internal TaskCompletionSource gitLogUpdated; + public Task GitLogUpdated => gitLogUpdated.Task; + internal TaskCompletionSource localBranchesUpdated; + public Task LocalBranchesUpdated => localBranchesUpdated.Task; + internal TaskCompletionSource remoteBranchesUpdated; + public Task RemoteBranchesUpdated => remoteBranchesUpdated.Task; + + + public RepositoryManagerEvents() + { + Reset(); + } public void Reset() { - IsBusy.Reset(); - IsNotBusy.Reset(); - CurrentBranchUpdated.Reset(); - GitAheadBehindStatusUpdated.Reset(); - GitStatusUpdated.Reset(); - GitLocksUpdated.Reset(); - GitLogUpdated.Reset(); - LocalBranchesUpdated.Reset(); - RemoteBranchesUpdated.Reset(); + isBusy = new TaskCompletionSource(); + isNotBusy = new TaskCompletionSource(); + currentBranchUpdated = new TaskCompletionSource(); + gitAheadBehindStatusUpdated = new TaskCompletionSource(); + gitStatusUpdated = new TaskCompletionSource(); + gitLocksUpdated = new TaskCompletionSource(); + gitLogUpdated = new TaskCompletionSource(); + localBranchesUpdated = new TaskCompletionSource(); + remoteBranchesUpdated = new TaskCompletionSource(); } - public void WaitForNotBusy(int seconds = 1) + public async Task WaitForNotBusy(int seconds = 1) { - IsBusy.WaitOne(TimeSpan.FromSeconds(seconds)); - IsNotBusy.WaitOne(TimeSpan.FromSeconds(seconds)); + await TaskEx.WhenAny(IsBusy, TaskEx.Delay(TimeSpan.FromSeconds(seconds))); + await TaskEx.WhenAny(IsNotBusy, TaskEx.Delay(TimeSpan.FromSeconds(seconds))); } } @@ -64,51 +80,51 @@ public static void AttachListener(this IRepositoryManagerListener listener, logger?.Trace("OnIsBusyChanged: {0}", isBusy); listener.OnIsBusyChanged(isBusy); if (isBusy) - managerEvents?.IsBusy.Set(); + managerEvents?.isBusy.TrySetResult(true); else - managerEvents?.IsNotBusy.Set(); + managerEvents?.isNotBusy.TrySetResult(true); }; repositoryManager.CurrentBranchUpdated += (configBranch, configRemote) => { logger?.Trace("CurrentBranchUpdated"); listener.CurrentBranchUpdated(configBranch, configRemote); - managerEvents?.CurrentBranchUpdated.Set(); + managerEvents?.currentBranchUpdated.TrySetResult(true); }; repositoryManager.GitLocksUpdated += gitLocks => { logger?.Trace("GitLocksUpdated"); listener.GitLocksUpdated(gitLocks); - managerEvents?.GitLocksUpdated.Set(); + managerEvents?.gitLocksUpdated.TrySetResult(true); }; repositoryManager.GitAheadBehindStatusUpdated += gitAheadBehindStatus => { logger?.Trace("GitAheadBehindStatusUpdated"); listener.GitAheadBehindStatusUpdated(gitAheadBehindStatus); - managerEvents?.GitAheadBehindStatusUpdated.Set(); + managerEvents?.gitAheadBehindStatusUpdated.TrySetResult(true); }; repositoryManager.GitStatusUpdated += gitStatus => { logger?.Trace("GitStatusUpdated"); listener.GitStatusUpdated(gitStatus); - managerEvents?.GitStatusUpdated.Set(); + managerEvents?.gitStatusUpdated.TrySetResult(true); }; repositoryManager.GitLogUpdated += gitLogEntries => { logger?.Trace("GitLogUpdated"); listener.GitLogUpdated(gitLogEntries); - managerEvents?.GitLogUpdated.Set(); + managerEvents?.gitLogUpdated.TrySetResult(true); }; repositoryManager.LocalBranchesUpdated += branchList => { logger?.Trace("LocalBranchesUpdated"); listener.LocalBranchesUpdated(branchList); - managerEvents?.LocalBranchesUpdated.Set(); + managerEvents?.localBranchesUpdated.TrySetResult(true); }; repositoryManager.RemoteBranchesUpdated += (remotesList, branchList) => { logger?.Trace("RemoteBranchesUpdated"); listener.RemoteBranchesUpdated(remotesList, branchList); - managerEvents?.RemoteBranchesUpdated.Set(); + managerEvents?.remoteBranchesUpdated.TrySetResult(true); }; } diff --git a/src/tests/TestUtils/TestUtils.csproj b/src/tests/TestUtils/TestUtils.csproj index 4dad1fe33..8ae36c41d 100644 --- a/src/tests/TestUtils/TestUtils.csproj +++ b/src/tests/TestUtils/TestUtils.csproj @@ -30,6 +30,10 @@ 4 + + ..\..\..\packages\AsyncBridge.Net35.0.2.3333.0\lib\net35-Client\AsyncBridge.Net35.dll + True + $(SolutionDir)packages\FluentAssertions.2.2.0.0\lib\net35\FluentAssertions.dll True diff --git a/src/tests/TestUtils/packages.config b/src/tests/TestUtils/packages.config index f785ed1a6..3c80d2337 100644 --- a/src/tests/TestUtils/packages.config +++ b/src/tests/TestUtils/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/tests/TestWebServer/TestWebServer.csproj b/src/tests/TestWebServer/TestWebServer.csproj index 82dd24397..54d84b98d 100644 --- a/src/tests/TestWebServer/TestWebServer.csproj +++ b/src/tests/TestWebServer/TestWebServer.csproj @@ -42,6 +42,40 @@ + + + files\git\windows\git.zip + PreserveNewest + + + files\git\windows\git.zip.md5 + PreserveNewest + + + files\git\windows\git-lfs.zip + PreserveNewest + + + files\git\windows\git-lfs.zip.md5 + PreserveNewest + + + + files\git\mac\git-lfs.zip + PreserveNewest + + + files\git\mac\git-lfs.zip.md5 + PreserveNewest + + {bb6a8eda-15d8-471b-a6ed-ee551e0b3ba0} diff --git a/src/tests/UnitTests/Authentication/KeychainTests.cs b/src/tests/UnitTests/Authentication/KeychainTests.cs index f86623ace..ba5244abd 100644 --- a/src/tests/UnitTests/Authentication/KeychainTests.cs +++ b/src/tests/UnitTests/Authentication/KeychainTests.cs @@ -223,7 +223,7 @@ public void ShouldDeleteFromCacheWhenLoadReturnsNullFromConnectionManager() var uriString = keychain.Hosts.FirstOrDefault(); var keychainAdapter = keychain.Load(uriString).Result; - keychainAdapter.Credential.Should().BeNull(); + keychainAdapter.Should().BeNull(); fileSystem.DidNotReceive().FileExists(Args.String); fileSystem.DidNotReceive().ReadAllText(Args.String); diff --git a/src/tests/UnitTests/IO/BranchListOutputProcessorTests.cs b/src/tests/UnitTests/IO/BranchListOutputProcessorTests.cs index 64d265bdf..f22cc237a 100644 --- a/src/tests/UnitTests/IO/BranchListOutputProcessorTests.cs +++ b/src/tests/UnitTests/IO/BranchListOutputProcessorTests.cs @@ -20,9 +20,9 @@ public void ShouldProcessOutput() AssertProcessOutput(output, new[] { - new GitBranch("master", "origin/master", true), - new GitBranch("feature/feature-1", "", false), - new GitBranch("bugfixes/bugfix-1", "origin/bugfixes/bugfix-1", false), + new GitBranch("master", "origin/master"), + new GitBranch("feature/feature-1", ""), + new GitBranch("bugfixes/bugfix-1", "origin/bugfixes/bugfix-1"), }); }