From edb884fc5d8f9c16e50f71e3ab43413cfc1f17f6 Mon Sep 17 00:00:00 2001 From: Andreia Gaita Date: Tue, 29 May 2018 11:53:42 +0200 Subject: [PATCH] Adding some ways of asking for a password when git asks for it in network operations SSH_ASKPASS/GIT_ASKPASS will ask for a password if there isn't one. If the command line asks for a password, use a custom UI to ask for it. --- .../Application/ApplicationManagerBase.cs | 4 +- .../Application/IApplicationManager.cs | 9 ++ src/GitHub.Api/Git/GitClient.cs | 12 +-- src/GitHub.Api/Git/IRepository.cs | 2 +- src/GitHub.Api/Git/Repository.cs | 53 ++++++++++- src/GitHub.Api/Git/RepositoryManager.cs | 21 +++-- src/GitHub.Api/Git/Tasks/GitFetchTask.cs | 2 +- src/GitHub.Api/Git/Tasks/GitPullTask.cs | 2 +- src/GitHub.Api/Git/Tasks/GitPushTask.cs | 4 +- .../BranchListOutputProcessor.cs | 7 +- .../GitAheadBehindStatusOutputProcessor.cs | 8 +- .../LfsVersionOutputProcessor.cs | 5 +- .../OutputProcessors/LocksOutputProcessor.cs | 5 +- .../LogEntryOutputProcessor.cs | 13 +-- .../RemoteListOutputProcessor.cs | 5 +- .../OutputProcessors/StatusOutputProcessor.cs | 3 +- .../VersionOutputProcessor.cs | 5 +- src/GitHub.Api/Platform/ProcessEnvironment.cs | 11 ++- src/GitHub.Api/Tasks/BaseOutputProcessor.cs | 32 +++++-- src/GitHub.Api/Tasks/ConfigOutputProcessor.cs | 7 +- src/GitHub.Api/Tasks/OctorunTask.cs | 5 +- src/GitHub.Api/Tasks/ProcessTask.cs | 93 ++++++++++++++----- .../Editor/GitHub.Unity/ApplicationManager.cs | 5 + .../GitHub.Unity/UI/AuthenticationView.cs | 77 ++++++++++++++- .../Editor/GitHub.Unity/UI/BaseWindow.cs | 3 +- .../Assets/Editor/GitHub.Unity/UI/IView.cs | 3 +- .../Editor/GitHub.Unity/UI/PopupWindow.cs | 38 ++++---- .../Editor/GitHub.Unity/UI/PublishView.cs | 4 +- .../Assets/Editor/GitHub.Unity/UI/Subview.cs | 17 ++-- .../Assets/Editor/GitHub.Unity/UI/Window.cs | 4 +- .../IntegrationTests/BaseIntegrationTest.cs | 2 +- .../Substitutes/SubstituteFactory.cs | 12 ++- 32 files changed, 348 insertions(+), 125 deletions(-) diff --git a/src/GitHub.Api/Application/ApplicationManagerBase.cs b/src/GitHub.Api/Application/ApplicationManagerBase.cs index 58d3f1eb9..a723d8345 100644 --- a/src/GitHub.Api/Application/ApplicationManagerBase.cs +++ b/src/GitHub.Api/Application/ApplicationManagerBase.cs @@ -295,12 +295,14 @@ public void RestartRepository() repositoryManager = Unity.RepositoryManager.CreateInstance(Platform, TaskManager, GitClient, Environment.RepositoryPath); repositoryManager.Initialize(); - Environment.Repository.Initialize(repositoryManager, TaskManager); + Environment.Repository.Initialize(repositoryManager, TaskManager, this); repositoryManager.Start(); Environment.Repository.Start(); Logger.Trace($"Got a repository? {(Environment.Repository != null ? Environment.Repository.LocalPath : "null")}"); } + public abstract void OpenPopupWindow(PopupViewType viewType, object data, Action onClose); + protected void SetupMetrics(string unityVersion) { string userId = null; diff --git a/src/GitHub.Api/Application/IApplicationManager.cs b/src/GitHub.Api/Application/IApplicationManager.cs index 9b8b5f638..cef2bf6a2 100644 --- a/src/GitHub.Api/Application/IApplicationManager.cs +++ b/src/GitHub.Api/Application/IApplicationManager.cs @@ -4,6 +4,14 @@ namespace GitHub.Unity { + public enum PopupViewType + { + None, + PublishView, + AuthenticationView, + PasswordView, + } + public interface IApplicationManager : IDisposable { IEnvironment Environment { get; } @@ -22,5 +30,6 @@ public interface IApplicationManager : IDisposable event Action OnProgress; void SetupGit(GitInstaller.GitInstallationState state); void RestartRepository(); + void OpenPopupWindow(PopupViewType viewType, object data, Action onClose); } } \ No newline at end of file diff --git a/src/GitHub.Api/Git/GitClient.cs b/src/GitHub.Api/Git/GitClient.cs index 9570920a9..7fe7fcf71 100644 --- a/src/GitHub.Api/Git/GitClient.cs +++ b/src/GitHub.Api/Git/GitClient.cs @@ -16,10 +16,10 @@ public interface IGitClient ITask SetConfig(string key, string value, GitConfigSource configSource, IOutputProcessor processor = null); ITask GetConfigUserAndEmail(); ITask> ListLocks(bool local, BaseOutputListProcessor processor = null); - ITask Pull(string remote, string branch, IOutputProcessor processor = null); - ITask Push(string remote, string branch, IOutputProcessor processor = null); + IProcessTask Pull(string remote, string branch, IOutputProcessor processor = null); + IProcessTask Push(string remote, string branch, IOutputProcessor processor = null); ITask Revert(string changeset, IOutputProcessor processor = null); - ITask Fetch(string remote, IOutputProcessor processor = null); + IProcessTask Fetch(string remote, IOutputProcessor processor = null); ITask SwitchBranch(string branch, IOutputProcessor processor = null); ITask DeleteBranch(string branch, bool deleteUnmerged = false, IOutputProcessor processor = null); ITask CreateBranch(string branch, string baseBranch, IOutputProcessor processor = null); @@ -146,13 +146,13 @@ public ITask> ListLocks(bool local, BaseOutputListProcessor Pull(string remote, string branch, IOutputProcessor processor = null) + public IProcessTask Pull(string remote, string branch, IOutputProcessor processor = null) { return new GitPullTask(remote, branch, cancellationToken, processor) .Configure(processManager); } - public ITask Push(string remote, string branch, + public IProcessTask Push(string remote, string branch, IOutputProcessor processor = null) { return new GitPushTask(remote, branch, true, cancellationToken, processor) @@ -165,7 +165,7 @@ public ITask Revert(string changeset, IOutputProcessor processor .Configure(processManager); } - public ITask Fetch(string remote, + public IProcessTask Fetch(string remote, IOutputProcessor processor = null) { return new GitFetchTask(remote, cancellationToken, processor: processor) diff --git a/src/GitHub.Api/Git/IRepository.cs b/src/GitHub.Api/Git/IRepository.cs index 1aa80c41f..64a246a23 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, IBackedByCache { - void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager); + void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager, IApplicationManager appManager); 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 33a10b1a4..a0add9b75 100644 --- a/src/GitHub.Api/Git/Repository.cs +++ b/src/GitHub.Api/Git/Repository.cs @@ -19,6 +19,7 @@ sealed class Repository : IEquatable, IRepository private IRepositoryManager repositoryManager; private ITaskManager taskManager; + private IApplicationManager appManager; private ICacheContainer cacheContainer; private UriString cloneUrl; private string name; @@ -80,13 +81,16 @@ public Repository(NPath localPath, ICacheContainer container) }; } - public void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager) + public void Initialize(IRepositoryManager theRepositoryManager, ITaskManager theTaskManager, IApplicationManager theAppManager) { Guard.ArgumentNotNull(theRepositoryManager, nameof(theRepositoryManager)); Guard.ArgumentNotNull(theTaskManager, nameof(theTaskManager)); + Guard.ArgumentNotNull(theAppManager, nameof(theAppManager)); this.taskManager = theTaskManager; this.repositoryManager = theRepositoryManager; + this.appManager = theAppManager; + this.repositoryManager.CurrentBranchUpdated += RepositoryManagerOnCurrentBranchUpdated; this.repositoryManager.GitStatusUpdated += RepositoryManagerOnGitStatusUpdated; this.repositoryManager.GitAheadBehindStatusUpdated += RepositoryManagerOnGitAheadBehindStatusUpdated; @@ -130,10 +134,33 @@ public ITask SetupRemote(string remote, string remoteUrl) public ITask CommitAllFiles(string message, string body) => repositoryManager.CommitAllFiles(message, body); public ITask CommitFiles(List files, string message, string body) => repositoryManager.CommitFiles(files, message, body); - public ITask Pull() => repositoryManager.Pull(CurrentRemote.Value.Name, CurrentBranch?.Name); - public ITask Push(string remote) => repositoryManager.Push(remote, CurrentBranch?.Name); - public ITask Push() => repositoryManager.Push(CurrentRemote.Value.Name, CurrentBranch?.Name); - public ITask Fetch() => repositoryManager.Fetch(CurrentRemote.Value.Name); + public ITask Pull() + { + var task = repositoryManager.Pull(CurrentRemote.Value.Name, CurrentBranch?.Name); + task.OnInput += HandleGitProcessInput; + return task.GetEndOfChain(); + } + public ITask Push(string remote) + { + var task = repositoryManager.Push(remote, CurrentBranch?.Name); + task.OnInput += HandleGitProcessInput; + return task.GetEndOfChain(); + } + + public ITask Push() + { + var task = repositoryManager.Push(CurrentRemote.Value.Name, CurrentBranch?.Name); + task.OnInput += HandleGitProcessInput; + return task.GetEndOfChain(); + } + + public ITask Fetch() + { + var task = repositoryManager.Fetch(CurrentRemote.Value.Name); + task.OnInput += HandleGitProcessInput; + return task.GetEndOfChain(); + } + public ITask Revert(string changeset) => repositoryManager.Revert(changeset); public ITask RequestLock(NPath file) => repositoryManager.LockFile(file); public ITask ReleaseLock(NPath file, bool force) => repositoryManager.UnlockFile(file, force); @@ -179,6 +206,22 @@ public bool Equals(IRepository other) return other != null && object.Equals(LocalPath, other.LocalPath); } + private void HandleGitProcessInput(IProcess proc, string input) + { + taskManager.RunInUI(() => { + appManager.OpenPopupWindow(PopupViewType.PasswordView, input, (success, output) => { + if (success) + { + proc.StandardInput.WriteLine(output); + } + else + { + proc.Stop(); + } + }); + }); + } + private void RefreshCache(CacheType cacheType) { taskManager.RunInUI(() => Refresh(cacheType)); diff --git a/src/GitHub.Api/Git/RepositoryManager.cs b/src/GitHub.Api/Git/RepositoryManager.cs index 865a66d26..6ff92c32e 100644 --- a/src/GitHub.Api/Git/RepositoryManager.cs +++ b/src/GitHub.Api/Git/RepositoryManager.cs @@ -24,9 +24,9 @@ public interface IRepositoryManager : IDisposable void Stop(); ITask CommitAllFiles(string message, string body); ITask CommitFiles(List files, string message, string body); - ITask Fetch(string remote); - ITask Pull(string remote, string branch); - ITask Push(string remote, string branch); + IProcessTask Fetch(string remote); + IProcessTask Pull(string remote, string branch); + IProcessTask Push(string remote, string branch); ITask Revert(string changeset); ITask RemoteAdd(string remote, string url); ITask RemoteRemove(string remote); @@ -192,22 +192,25 @@ public ITask CommitFiles(List files, string message, string body) return HookupHandlers(task, true); } - public ITask Fetch(string remote) + public IProcessTask Fetch(string remote) { var task = GitClient.Fetch(remote); - return HookupHandlers(task, false); + HookupHandlers(task, false); + return task; } - public ITask Pull(string remote, string branch) + public IProcessTask Pull(string remote, string branch) { var task = GitClient.Pull(remote, branch); - return HookupHandlers(task, true); + HookupHandlers(task, true); + return task; } - public ITask Push(string remote, string branch) + public IProcessTask Push(string remote, string branch) { var task = GitClient.Push(remote, branch); - return HookupHandlers(task, false); + HookupHandlers(task, false); + return task; } public ITask Revert(string changeset) diff --git a/src/GitHub.Api/Git/Tasks/GitFetchTask.cs b/src/GitHub.Api/Git/Tasks/GitFetchTask.cs index 39a6421bb..2ff2cae4f 100644 --- a/src/GitHub.Api/Git/Tasks/GitFetchTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitFetchTask.cs @@ -11,7 +11,7 @@ class GitFetchTask : ProcessTask public GitFetchTask(string remote, CancellationToken token, bool prune = true, bool tags = true, IOutputProcessor processor = null) - : base(token, processor ?? new SimpleOutputProcessor()) + : base(token, processor ?? new GitNetworkOperationOutputProcessor()) { Name = TaskName; var args = new List { "fetch" }; diff --git a/src/GitHub.Api/Git/Tasks/GitPullTask.cs b/src/GitHub.Api/Git/Tasks/GitPullTask.cs index 64006d950..43eedddfd 100644 --- a/src/GitHub.Api/Git/Tasks/GitPullTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitPullTask.cs @@ -11,7 +11,7 @@ class GitPullTask : ProcessTask public GitPullTask(string remote, string branch, CancellationToken token, IOutputProcessor processor = null) - : base(token, processor ?? new SimpleOutputProcessor()) + : base(token, processor ?? new GitNetworkOperationOutputProcessor()) { Name = TaskName; var stringBuilder = new StringBuilder(); diff --git a/src/GitHub.Api/Git/Tasks/GitPushTask.cs b/src/GitHub.Api/Git/Tasks/GitPushTask.cs index 7e786c940..82923bc82 100644 --- a/src/GitHub.Api/Git/Tasks/GitPushTask.cs +++ b/src/GitHub.Api/Git/Tasks/GitPushTask.cs @@ -9,7 +9,7 @@ class GitPushTask : ProcessTask private readonly string arguments; public GitPushTask(CancellationToken token, IOutputProcessor processor = null) - : base(token, processor ?? new SimpleOutputProcessor()) + : base(token, processor ?? new GitNetworkOperationOutputProcessor()) { Name = TaskName; arguments = "push"; @@ -17,7 +17,7 @@ public GitPushTask(CancellationToken token, IOutputProcessor processor = public GitPushTask(string remote, string branch, bool setUpstream, CancellationToken token, IOutputProcessor processor = null) - : base(token, processor ?? new SimpleOutputProcessor()) + : base(token, processor ?? new GitNetworkOperationOutputProcessor()) { Guard.ArgumentNotNullOrWhiteSpace(remote, "remote"); Guard.ArgumentNotNullOrWhiteSpace(branch, "branch"); diff --git a/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs index 320ea9435..12e5647c4 100644 --- a/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/BranchListOutputProcessor.cs @@ -6,14 +6,14 @@ class BranchListOutputProcessor : BaseOutputListProcessor { private static readonly Regex trackingBranchRegex = new Regex(@"\[[\w]+\/.*\]"); - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) - return; + return false; var proc = new LineParser(line); if (proc.IsAtEnd) - return; + return false; var active = proc.Matches('*'); proc.SkipWhitespace(); @@ -39,6 +39,7 @@ public override void LineReceived(string line) var branch = new GitBranch(name, trackingName); RaiseOnEntry(branch); + return false; } } } \ No newline at end of file diff --git a/src/GitHub.Api/OutputProcessors/GitAheadBehindStatusOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/GitAheadBehindStatusOutputProcessor.cs index 1e3808ffa..7d4b55477 100644 --- a/src/GitHub.Api/OutputProcessors/GitAheadBehindStatusOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/GitAheadBehindStatusOutputProcessor.cs @@ -2,19 +2,17 @@ namespace GitHub.Unity { class GitAheadBehindStatusOutputProcessor : BaseOutputProcessor { - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) - { - return; - } + return false; var proc = new LineParser(line); - var ahead = int.Parse(proc.ReadUntilWhitespace()); var behind = int.Parse(proc.ReadToEnd()); RaiseOnEntry(new GitAheadBehindStatus(ahead, behind)); + return false; } } } \ No newline at end of file diff --git a/src/GitHub.Api/OutputProcessors/LfsVersionOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/LfsVersionOutputProcessor.cs index 465747c60..b2af0cf81 100644 --- a/src/GitHub.Api/OutputProcessors/LfsVersionOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/LfsVersionOutputProcessor.cs @@ -5,10 +5,10 @@ namespace GitHub.Unity { class LfsVersionOutputProcessor : BaseOutputProcessor { - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (String.IsNullOrEmpty(line)) - return; + return false; var parts = line.Split('/', ' '); if (parts.Length > 1) @@ -16,6 +16,7 @@ public override void LineReceived(string line) var version = TheVersion.Parse(parts[1]); RaiseOnEntry(version); } + return false; } } } \ No newline at end of file diff --git a/src/GitHub.Api/OutputProcessors/LocksOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/LocksOutputProcessor.cs index 2c27c2cb1..2d500ceee 100644 --- a/src/GitHub.Api/OutputProcessors/LocksOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/LocksOutputProcessor.cs @@ -4,12 +4,12 @@ namespace GitHub.Unity { class LocksOutputProcessor : BaseOutputListProcessor { - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (string.IsNullOrEmpty(line)) { //Do Nothing - return; + return false; } try @@ -24,6 +24,7 @@ public override void LineReceived(string line) { Logger.Error(ex, $"Failed to parse lock line {line}"); } + return false; } } } diff --git a/src/GitHub.Api/OutputProcessors/LogEntryOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/LogEntryOutputProcessor.cs index 4ad7f6560..b3360d2ce 100644 --- a/src/GitHub.Api/OutputProcessors/LogEntryOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/LogEntryOutputProcessor.cs @@ -55,12 +55,12 @@ private void Reset() seenBodyEnd = false; } - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) { ReturnGitLogEntry(); - return; + return false; } sb.AppendLine(line); @@ -178,13 +178,13 @@ public override void LineReceived(string line) if (string.IsNullOrEmpty(line)) { ReturnGitLogEntry(); - return; + return false; } if (line.IndexOf("---GHUBODYEND---", StringComparison.InvariantCulture) >= 0) { seenBodyEnd = true; - return; + return false; } var proc = new LineParser(line); @@ -233,12 +233,12 @@ public override void LineReceived(string line) { // there's no files on this commit, it's a new one! ReturnGitLogEntry(); - return; + return false; } else { HandleUnexpected(line); - return; + return false; } switch (status) @@ -292,6 +292,7 @@ public override void LineReceived(string line) HandleUnexpected(line); break; } + return false; } private void PopNewlines() diff --git a/src/GitHub.Api/OutputProcessors/RemoteListOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/RemoteListOutputProcessor.cs index 03f8f9d68..4c863251d 100644 --- a/src/GitHub.Api/OutputProcessors/RemoteListOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/RemoteListOutputProcessor.cs @@ -14,14 +14,14 @@ public RemoteListOutputProcessor() Reset(); } - public override void LineReceived(string line) + public override bool LineReceived(string line) { //origin https://github.com/github/VisualStudio.git (fetch) if (line == null) { ReturnRemote(); - return; + return false; } var proc = new LineParser(line); @@ -52,6 +52,7 @@ public override void LineReceived(string line) currentUrl = url; currentModes.Add(mode); } + return false; } private void ReturnRemote() diff --git a/src/GitHub.Api/OutputProcessors/StatusOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/StatusOutputProcessor.cs index ad1e866d9..08ade3aeb 100644 --- a/src/GitHub.Api/OutputProcessors/StatusOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/StatusOutputProcessor.cs @@ -19,7 +19,7 @@ public GitStatusOutputProcessor(IGitObjectFactory gitObjectFactory) this.gitObjectFactory = gitObjectFactory; } - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) { @@ -183,6 +183,7 @@ public override void LineReceived(string line) gitStatus.Entries.Add(gitStatusEntry); } } + return false; } private void ReturnStatus() diff --git a/src/GitHub.Api/OutputProcessors/VersionOutputProcessor.cs b/src/GitHub.Api/OutputProcessors/VersionOutputProcessor.cs index 901098c35..67e0351fd 100644 --- a/src/GitHub.Api/OutputProcessors/VersionOutputProcessor.cs +++ b/src/GitHub.Api/OutputProcessors/VersionOutputProcessor.cs @@ -9,10 +9,10 @@ class VersionOutputProcessor : BaseOutputProcessor { public static Regex GitVersionRegex = new Regex(@"git version (.*)"); - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (String.IsNullOrEmpty(line)) - return; + return false; var match = GitVersionRegex.Match(line); if (match.Groups.Count > 1) @@ -20,6 +20,7 @@ public override void LineReceived(string line) var version = TheVersion.Parse(match.Groups[1].Value); RaiseOnEntry(version); } + return false; } } } \ No newline at end of file diff --git a/src/GitHub.Api/Platform/ProcessEnvironment.cs b/src/GitHub.Api/Platform/ProcessEnvironment.cs index 0f094f2cb..70b2f6eb3 100644 --- a/src/GitHub.Api/Platform/ProcessEnvironment.cs +++ b/src/GitHub.Api/Platform/ProcessEnvironment.cs @@ -75,7 +75,14 @@ public void Configure(ProcessStartInfo psi, NPath workingDirectory, bool dontSet // 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 (libexecPath.IsInitialized) + { psi.EnvironmentVariables["GIT_EXEC_PATH"] = libexecPath.ToString(); + if (Environment.IsWindows) + { + psi.EnvironmentVariables["GIT_ASKPASS"] = libexecPath.Combine("git-askpass.exe"); + psi.EnvironmentVariables["SSH_ASKPASS"] = libexecPath.Combine("git-askpass.exe"); + } + } } if (Environment.GitLfsInstallPath.IsInitialized && libexecPath != Environment.GitLfsInstallPath) @@ -108,6 +115,8 @@ public void Configure(ProcessStartInfo psi, NPath workingDirectory, bool dontSet var httpsProxy = Environment.GetEnvironmentVariable("HTTPS_PROXY"); if (!String.IsNullOrEmpty(httpsProxy)) psi.EnvironmentVariables["HTTPS_PROXY"] = httpsProxy; + + psi.EnvironmentVariables["DISPLAY"] = "0"; } } -} \ No newline at end of file +} diff --git a/src/GitHub.Api/Tasks/BaseOutputProcessor.cs b/src/GitHub.Api/Tasks/BaseOutputProcessor.cs index b0b0342e5..9b1115708 100644 --- a/src/GitHub.Api/Tasks/BaseOutputProcessor.cs +++ b/src/GitHub.Api/Tasks/BaseOutputProcessor.cs @@ -7,7 +7,7 @@ namespace GitHub.Unity { public interface IOutputProcessor { - void LineReceived(string line); + bool LineReceived(string line); } public interface IOutputProcessor : IOutputProcessor @@ -25,7 +25,7 @@ public abstract class BaseOutputProcessor : IOutputProcessor { public event Action OnEntry; - public abstract void LineReceived(string line); + public abstract bool LineReceived(string line); protected void RaiseOnEntry(T entry) { Result = entry; @@ -63,30 +63,32 @@ protected override void RaiseOnEntry(T entry) class SimpleOutputProcessor : BaseOutputProcessor { private readonly StringBuilder sb = new StringBuilder(); - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) - return; + return false; sb.AppendLine(line); RaiseOnEntry(line); + return false; } public override string Result { get { return sb.ToString(); } } } class SimpleListOutputProcessor : BaseOutputListProcessor { - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) - return; + return false; RaiseOnEntry(line); + return false; } } abstract class FirstResultOutputProcessor : BaseOutputProcessor { private bool isSet = false; - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (!isSet) { @@ -98,6 +100,7 @@ public override void LineReceived(string line) RaiseOnEntry(res); } } + return false; } protected abstract bool ProcessLine(string line, out T result); @@ -126,4 +129,19 @@ protected override bool ProcessLine(string line, out NPath result) return true; } } + + class GitNetworkOperationOutputProcessor : BaseOutputProcessor + { + private readonly StringBuilder sb = new StringBuilder(); + public override bool LineReceived(string line) + { + if (line == null) + return false; + sb.AppendLine(line); + RaiseOnEntry(line); + return line.StartsWith("Enter"); + } + public override string Result { get { return sb.ToString(); } } + } + } \ No newline at end of file diff --git a/src/GitHub.Api/Tasks/ConfigOutputProcessor.cs b/src/GitHub.Api/Tasks/ConfigOutputProcessor.cs index 16f23f645..722dff2c0 100644 --- a/src/GitHub.Api/Tasks/ConfigOutputProcessor.cs +++ b/src/GitHub.Api/Tasks/ConfigOutputProcessor.cs @@ -5,18 +5,19 @@ namespace GitHub.Unity { class ConfigOutputProcessor : BaseOutputListProcessor> { - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (String.IsNullOrEmpty(line)) - return; + return false; var eqs = line.IndexOf("="); if (eqs <= 0) { - return; + return false; } var kvp = new KeyValuePair(line.Substring(0, eqs), line.Substring(eqs + 1)); RaiseOnEntry(kvp); + return false; } } } \ No newline at end of file diff --git a/src/GitHub.Api/Tasks/OctorunTask.cs b/src/GitHub.Api/Tasks/OctorunTask.cs index f64fcc0cc..1e9419b3f 100644 --- a/src/GitHub.Api/Tasks/OctorunTask.cs +++ b/src/GitHub.Api/Tasks/OctorunTask.cs @@ -13,7 +13,7 @@ class OctorunResultOutputProcessor : BaseOutputProcessor private string status; private List output = new List(); - public override void LineReceived(string line) + public override bool LineReceived(string line) { if (line == null) { @@ -27,7 +27,7 @@ public override void LineReceived(string line) octorunResult = new OctorunResult(status, output.ToArray()); } RaiseOnEntry(octorunResult); - return; + return false; } if (lineCount == 0) @@ -47,6 +47,7 @@ public override void LineReceived(string line) } lineCount++; + return false; } } diff --git a/src/GitHub.Api/Tasks/ProcessTask.cs b/src/GitHub.Api/Tasks/ProcessTask.cs index a5aa3c103..12c88f6d9 100644 --- a/src/GitHub.Api/Tasks/ProcessTask.cs +++ b/src/GitHub.Api/Tasks/ProcessTask.cs @@ -6,7 +6,6 @@ using System.IO; using System.Text; using System.Threading; -using System.Threading.Tasks; namespace GitHub.Unity { @@ -39,6 +38,7 @@ public interface IProcess { void Configure(Process existingProcess); void Configure(ProcessStartInfo psi); + void Stop(); event Action OnErrorData; StreamWriter StandardInput { get; } int ProcessId { get; } @@ -49,9 +49,10 @@ public interface IProcess event Action OnEndProcess; } - interface IProcessTask : ITask, IProcess + public interface IProcessTask : ITask, IProcess { void Configure(ProcessStartInfo psi, IOutputProcessor processor); + event Action OnInput; } interface IProcessTask : ITask, IProcess @@ -65,6 +66,7 @@ class ProcessWrapper private readonly IOutputProcessor outputProcessor; private readonly Action onStart; private readonly Action onEnd; + private readonly Action onInput; private readonly Action onError; private readonly CancellationToken token; private readonly List errors = new List(); @@ -76,13 +78,14 @@ class ProcessWrapper protected ILogging Logger { get { return logger = logger ?? LogHelper.GetLogger(GetType()); } } public ProcessWrapper(string taskName, Process process, IOutputProcessor outputProcessor, - Action onStart, Action onEnd, Action onError, + Action onStart, Action onEnd, Action onInput, Action onError, CancellationToken token) { this.taskName = taskName; this.outputProcessor = outputProcessor; this.onStart = onStart; this.onEnd = onEnd; + this.onInput = onInput; this.onError = onError; this.token = token; this.Process = process; @@ -125,22 +128,38 @@ public void Run() if (Process.StartInfo.RedirectStandardOutput) { var outputStream = Process.StandardOutput; - var line = outputStream.ReadLine(); - while (line != null) + + var readThread = new Thread(() => { - outputProcessor.LineReceived(line); + var line = outputStream.ReadLine(); + while (line != null) + { + var needsInput = outputProcessor.LineReceived(line); + + if (token.IsCancellationRequested) + { + Stop(); + token.ThrowIfCancellationRequested(); + } + if (needsInput) + { + onInput(line); + } + + line = outputStream.ReadLine(); + } + outputProcessor.LineReceived(null); + }); + readThread.Start(); + while (!readThread.Join(1000)) + { if (token.IsCancellationRequested) { - if (!Process.HasExited) - Process.Kill(); - Process.Close(); - token.ThrowIfCancellationRequested(); + Stop(); } - - line = outputStream.ReadLine(); + token.ThrowIfCancellationRequested(); } - outputProcessor.LineReceived(null); } if (Process.StartInfo.CreateNoWindow) @@ -149,8 +168,7 @@ public void Run() { if (token.IsCancellationRequested) { - Process.Kill(); - Process.Close(); + Stop(); } token.ThrowIfCancellationRequested(); } @@ -189,6 +207,13 @@ public void Run() onEnd?.Invoke(); } + public void Stop() + { + if (!Process.HasExited) + Process.Kill(); + Process.Close(); + } + private bool WaitForExit(int milliseconds) { //Logger.Debug("WaitForExit - time: {0}ms", milliseconds); @@ -217,6 +242,7 @@ class ProcessTask : TaskBase, IProcessTask public event Action OnErrorData; public event Action OnStartProcess; public event Action OnEndProcess; + public event Action OnInput; private Exception thrownException = null; @@ -278,6 +304,11 @@ public void Configure(Process existingProcess) Name = ProcessArguments; } + public void Stop() + { + wrapper?.Stop(); + } + protected override void RaiseOnStart() { base.RaiseOnStart(); @@ -299,8 +330,8 @@ public override T RunWithReturn(bool success) var result = base.RunWithReturn(success); wrapper = new ProcessWrapper(Name, Process, outputProcessor, - RaiseOnStart, - () => + onStart: RaiseOnStart, + onEnd: () => { try { @@ -331,12 +362,16 @@ public override T RunWithReturn(bool success) RaiseOnEnd(result); } }, - (ex, error) => + onError: (ex, error) => { thrownException = ex; Errors = error; }, - Token); + onInput: input => + { + OnInput?.Invoke(this, input); + }, + token: Token); wrapper.Run(); @@ -365,6 +400,7 @@ class ProcessTaskWithListOutput : DataTaskBase>, IProcessTask OnErrorData; public event Action OnStartProcess; public event Action OnEndProcess; + public event Action OnInput; public ProcessTaskWithListOutput(CancellationToken token) : base(token) @@ -409,6 +445,11 @@ public virtual void Configure(ProcessStartInfo psi, IOutputProcessor> ProcessName = psi.FileName; } + public void Stop() + { + wrapper?.Stop(); + } + protected override void RaiseOnStart() { base.RaiseOnStart(); @@ -435,8 +476,8 @@ public override List RunWithReturn(bool success) var result = base.RunWithReturn(success); wrapper = new ProcessWrapper(Name, Process, outputProcessor, - RaiseOnStart, - () => + onStart: RaiseOnStart, + onEnd: () => { try { @@ -466,12 +507,16 @@ public override List RunWithReturn(bool success) RaiseOnEnd(result); } }, - (ex, error) => + onError: (ex, error) => { thrownException = ex; Errors = error; }, - Token); + onInput: input => + { + OnInput?.Invoke(this, input); + }, + token: Token); wrapper.Run(); return result; @@ -543,4 +588,4 @@ public SimpleListProcessTask(CancellationToken token, NPath fullPathToExecutable public override string ProcessName => fullPathToExecutable; public override string ProcessArguments => arguments; } -} \ No newline at end of file +} diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs index e3c6a2742..6776decc7 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/ApplicationManager.cs @@ -25,6 +25,11 @@ public ApplicationManager(IMainThreadSynchronizationContext synchronizationConte Initialize(); } + public override void OpenPopupWindow(PopupViewType viewType, object data, Action onClose) + { + PopupWindow.OpenWindow(viewType, data, onClose); + } + protected override void InitializeUI() { Logger.Trace("Restarted {0}", Environment.Repository != null ? Environment.Repository.LocalPath : "null"); diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/AuthenticationView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/AuthenticationView.cs index 9436f0779..480fe3d59 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/AuthenticationView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/AuthenticationView.cs @@ -5,6 +5,81 @@ namespace GitHub.Unity { + [Serializable] + class PasswordView : Subview + { + private const string WindowTitle = "Enter password"; + private const string PasswordLabel = "Password"; + private static readonly Vector2 viewSize = new Vector2(290, 290); + + [SerializeField] private string message; + [NonSerialized] private bool enterPressed; + [NonSerialized] private bool escPressed; + [NonSerialized] private string password = string.Empty; + + public override void InitializeView(IView parent) + { + base.InitializeView(parent); + Title = WindowTitle; + Size = viewSize; + } + + public override void OnDataUpdate() + { + base.OnDataUpdate(); + message = (string)Data; + } + + public override void OnGUI() + { + HandleKeyPresses(); + + EditorGUIUtility.labelWidth = 90f; + + GUILayout.BeginVertical(); + { + EditorGUILayout.HelpBox(message, MessageType.Warning); + password = EditorGUILayout.PasswordField(PasswordLabel, password, Styles.TextFieldStyle); + + GUILayout.Space(Styles.BaseSpacing + 3); + + EditorGUI.BeginDisabledGroup(String.IsNullOrEmpty(password)); + GUILayout.BeginHorizontal(); + { + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Localization.Ok) || enterPressed) + { + GUI.FocusControl(null); + Finish(true, password); + } + + if (GUILayout.Button(Localization.Cancel) || escPressed) + { + GUI.FocusControl(null); + Finish(false, null); + } + } + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + } + GUILayout.EndVertical(); + } + + private void HandleKeyPresses() + { + if (Event.current.type != EventType.KeyDown) + return; + + enterPressed = Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter; + if (enterPressed) + Event.current.Use(); + + escPressed = Event.current.keyCode == KeyCode.Escape; + if (escPressed) + Event.current.Use(); + } + } + [Serializable] class AuthenticationView : Subview { @@ -212,7 +287,7 @@ private void DoResult(bool success, string msg) TaskManager.Run(UsageTracker.IncrementAuthenticationViewButtonAuthentication, null); Clear(); - Finish(true); + Finish(true, null); } else { diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs index 9d2836ad9..7bb630db5 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/BaseWindow.cs @@ -38,7 +38,7 @@ public virtual void Refresh() { } - public virtual void Finish(bool result) + public virtual void Finish(bool result, object output) {} public virtual void Awake() @@ -133,6 +133,7 @@ public virtual void UpdateProgress(IProgress progress) public bool HasRepository { get { return Repository != null; } } public IUser User { get { return cachedUser; } } public bool HasUser { get { return User != null; } } + public object Data { get; protected set; } protected ITaskManager TaskManager { get { return Manager.TaskManager; } } protected IGitClient GitClient { get { return Manager.GitClient; } } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/IView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/IView.cs index f20b1e6e2..0267920d1 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/IView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/IView.cs @@ -12,7 +12,7 @@ interface IView : IUIEmpty, IUIProgress void DoneRefreshing(); Rect Position { get; } - void Finish(bool result); + void Finish(bool result, object output); IRepository Repository { get; } bool HasRepository { get; } IUser User { get; } @@ -21,6 +21,7 @@ interface IView : IUIEmpty, IUIProgress bool IsBusy { get; } bool IsRefreshing { get; } bool HasFocus { get; } + object Data { get; } } interface IUIEmpty diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs index ea7cd5e6d..9d98a5ecb 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PopupWindow.cs @@ -7,13 +7,6 @@ namespace GitHub.Unity [Serializable] class PopupWindow : BaseWindow { - public enum PopupViewType - { - None, - PublishView, - AuthenticationView, - } - [NonSerialized] private IApiClient client; [SerializeField] private PopupViewType activeViewType; @@ -22,13 +15,13 @@ public enum PopupViewType [SerializeField] private PublishView publishView; [SerializeField] private bool shouldCloseOnFinish; - public event Action OnClose; + public event Action OnClose; - public static PopupWindow OpenWindow(PopupViewType popupViewType, Action onClose = null) + public static PopupWindow OpenWindow(PopupViewType popupViewType, object data, Action onClose) { var popupWindow = GetWindow(true); - popupWindow.Open(popupViewType, onClose); + popupWindow.Open(popupViewType, data, onClose); return popupWindow; } @@ -87,9 +80,9 @@ public override void OnSelectionChange() ActiveView.OnSelectionChange(); } - public override void Finish(bool result) + public override void Finish(bool result, object output) { - OnClose.SafeInvoke(result); + OnClose.SafeInvoke(result, output); OnClose = null; if (shouldCloseOnFinish) @@ -98,19 +91,19 @@ public override void Finish(bool result) Close(); } - base.Finish(result); + base.Finish(result, output); } public override void OnDestroy() { base.OnDestroy(); - OnClose.SafeInvoke(false); + OnClose.SafeInvoke(false, null); OnClose = null; } - private void Open(PopupViewType popupViewType, Action onClose) + private void Open(PopupViewType popupViewType, object data, Action onClose) { - OnClose.SafeInvoke(false); + OnClose.SafeInvoke(false, null); OnClose = null; var viewNeedsAuthentication = popupViewType == PopupViewType.PublishView; @@ -118,18 +111,18 @@ private void Open(PopupViewType popupViewType, Action onClose) { Client.GetCurrentUser(user => { - OpenInternal(popupViewType, onClose); + OpenInternal(popupViewType, data, onClose); shouldCloseOnFinish = true; }, exception => { authenticationView.Initialize(exception); - OpenInternal(PopupViewType.AuthenticationView, completedAuthentication => + OpenInternal(PopupViewType.AuthenticationView, data, (success, output) => { - if (completedAuthentication) + if (success) { - Open(popupViewType, onClose); + Open(popupViewType, data, onClose); } }); shouldCloseOnFinish = false; @@ -137,18 +130,19 @@ private void Open(PopupViewType popupViewType, Action onClose) } else { - OpenInternal(popupViewType, onClose); + OpenInternal(popupViewType, data, onClose); shouldCloseOnFinish = true; } } - private void OpenInternal(PopupViewType popupViewType, Action onClose) + private void OpenInternal(PopupViewType popupViewType, object data, Action onClose) { if (onClose != null) { OnClose += onClose; } + Data = data; var fromView = ActiveView; ActiveViewType = popupViewType; SwitchView(fromView, ActiveView); diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs index 16822aca5..90e3a0ee7 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/PublishView.cs @@ -117,7 +117,7 @@ private void LoadOwners() var keychainEmptyException = exception as KeychainEmptyException; if (keychainEmptyException != null) { - PopupWindow.OpenWindow(PopupWindow.PopupViewType.AuthenticationView); + PopupWindow.OpenWindow(PopupViewType.AuthenticationView, null, null); return; } @@ -178,7 +178,7 @@ public override void OnGUI() } Repository.RemoteAdd("origin", repository.CloneUrl) .Then(Repository.Push("origin")) - .ThenInUI(Finish) + .ThenInUI(s => Finish(s, null)) .Start(); }, organization); diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Subview.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Subview.cs index dc09a6e6c..22ee5cab2 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Subview.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Subview.cs @@ -46,9 +46,9 @@ public virtual void Redraw() Parent.Redraw(); } - public virtual void Finish(bool result) + public virtual void Finish(bool result, object output) { - Parent.Finish(result); + Parent.Finish(result, output); } public void DoEmptyGUI() @@ -97,25 +97,28 @@ public void DoneRefreshing() } protected IView Parent { get; private set; } + public IApplicationManager Manager { get { return Parent.Manager; } } public IRepository Repository { get { return Parent.Repository; } } public bool HasRepository { get { return Parent.HasRepository; } } public IUser User { get { return Parent.User; } } public bool HasUser { get { return Parent.HasUser; } } + protected ITaskManager TaskManager { get { return Manager.TaskManager; } } + protected IGitClient GitClient { get { return Manager.GitClient; } } + protected IEnvironment Environment { get { return Manager.Environment; } } + protected IPlatform Platform { get { return Manager.Platform; } } + protected IUsageTracker UsageTracker { get { return Manager.UsageTracker; } } + public bool HasFocus { get { return Parent != null && Parent.HasFocus; } } public virtual bool IsBusy { get { return (Manager != null && Manager.IsBusy) || (Repository != null && Repository.IsBusy); } } - protected ITaskManager TaskManager { get { return Manager.TaskManager; } } - protected IGitClient GitClient { get { return Manager.GitClient; } } - protected IEnvironment Environment { get { return Manager.Environment; } } - protected IPlatform Platform { get { return Manager.Platform; } } - protected IUsageTracker UsageTracker { get { return Manager.UsageTracker; } } public Rect Position { get { return Parent.Position; } } public string Title { get; protected set; } public Vector2 Size { get; protected set; } + public object Data { get { return Parent.Data; } } protected Dictionary RefreshEvents { get; set; } public bool IsRefreshing { get; set; } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs index 34e2b5228..a430d96c7 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/Window.cs @@ -599,7 +599,7 @@ private void DoActionbarGUI() // Publishing a repo if (GUILayout.Button(Localization.PublishButton, Styles.ToolbarButtonStyle)) { - PopupWindow.OpenWindow(PopupWindow.PopupViewType.PublishView); + PopupWindow.OpenWindow(PopupViewType.PublishView, null, null); } } @@ -792,7 +792,7 @@ private void DoAccountDropdown() private void SignIn(object obj) { - PopupWindow.OpenWindow(PopupWindow.PopupViewType.AuthenticationView); + PopupWindow.OpenWindow(PopupViewType.AuthenticationView, null, null); } private void GoToProfile(object obj) diff --git a/src/tests/IntegrationTests/BaseIntegrationTest.cs b/src/tests/IntegrationTests/BaseIntegrationTest.cs index 6436d4950..0665b6856 100644 --- a/src/tests/IntegrationTests/BaseIntegrationTest.cs +++ b/src/tests/IntegrationTests/BaseIntegrationTest.cs @@ -115,7 +115,7 @@ protected IEnvironment InitializePlatformAndEnvironment(NPath repoPath, onRepositoryManagerCreated?.Invoke(RepositoryManager); - Environment.Repository?.Initialize(RepositoryManager, TaskManager); + Environment.Repository?.Initialize(RepositoryManager, TaskManager, ApplicationManager); RepositoryManager.Start(); Environment.Repository?.Start(); diff --git a/src/tests/TestUtils/Substitutes/SubstituteFactory.cs b/src/tests/TestUtils/Substitutes/SubstituteFactory.cs index 29e1690cc..a6190d168 100644 --- a/src/tests/TestUtils/Substitutes/SubstituteFactory.cs +++ b/src/tests/TestUtils/Substitutes/SubstituteFactory.cs @@ -355,7 +355,11 @@ public IGitClient CreateRepositoryProcessRunner( remote, branch, result != null ? result : "[null]"); - return new FuncTask(CancellationToken.None, _ => result); + var ret = Substitute.For>(); + ret.Result.Returns(result); + ret.Successful.Returns(true); + ret.IsCompleted.Returns(true); + return ret; }); gitClient.Push(Args.String, Args.String) @@ -369,7 +373,11 @@ public IGitClient CreateRepositoryProcessRunner( remote, branch, result != null ? result : "[null]"); - return new FuncTask(CancellationToken.None, _ => result); + var ret = Substitute.For>(); + ret.Result.Returns(result); + ret.Successful.Returns(true); + ret.IsCompleted.Returns(true); + return ret; }); gitClient.GetConfig(Args.String, Args.GitConfigSource)