Skip to content
Snippets Groups Projects
Commit 0730de15 authored by Andreia Gaita's avatar Andreia Gaita
Browse files

Splitting process management from task management

parent 92b50de7
No related branches found
No related tags found
No related merge requests found
Showing
with 723 additions and 8 deletions
......@@ -22,6 +22,7 @@
<WarningLevel>4</WarningLevel>
<RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisRuleSet>..\common\GitHub.ruleset</CodeAnalysisRuleSet>
<LangVersion>4</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
......@@ -49,7 +50,21 @@
</ItemGroup>
<ItemGroup>
<Compile Include="EntryPoint.cs" />
<Compile Include="Guard.cs" />
<Compile Include="Ensure.cs" />
<Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="IO\BaseOutputProcessor.cs" />
<Compile Include="IO\BranchListOutputProcessor.cs" />
<Compile Include="IO\DefaultEnvironment.cs" />
<Compile Include="IO\FileSystem.cs" />
<Compile Include="IO\GitEnvironment.cs" />
<Compile Include="IO\IEnvironment.cs" />
<Compile Include="IO\IFileSystem.cs" />
<Compile Include="IO\IGitEnvironment.cs" />
<Compile Include="IO\IOutputProcessor.cs" />
<Compile Include="IO\IProcess.cs" />
<Compile Include="Helpers\LineProcessor.cs" />
<Compile Include="IO\ProcessOutputManager.cs" />
<Compile Include="IO\ProcessWrapper.cs" />
<Compile Include="Installer.cs" />
<Compile Include="Localization.Designer.cs">
<AutoGen>True</AutoGen>
......@@ -83,6 +98,7 @@
<Compile Include="Tasks\GitSwitchBranchesTask.cs" />
<Compile Include="Tasks\GitTask.cs" />
<Compile Include="Tasks\ITask.cs" />
<Compile Include="IO\ProcessManager.cs" />
<Compile Include="Tasks\ProcessTask.cs" />
<Compile Include="Tasks\TaskException.cs" />
<Compile Include="Tasks\TaskQueueSetting.cs" />
......
......@@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Unity", "GitHub.Unity.csproj", "{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOTests", "..\IOTests\IOTests.csproj", "{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -15,6 +17,10 @@ Global
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADD7A18B-DD2A-4C22-A2C1-488964EFF30A}.Release|Any CPU.Build.0 = Release|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
using System;
using System.Text.RegularExpressions;
namespace GitHub.Unity
{
class LineParser
{
readonly private string line;
private int current;
public LineParser(string line)
{
this.line = line;
current = 0;
}
public bool Matches(string search)
{
return line.Substring(current).StartsWith(search);
}
public bool Matches(char search)
{
return line[current] == search;
}
public bool Matches(Regex regex)
{
return regex.IsMatch(line, current);
}
public void MoveNext()
{
current++;
}
public void MoveToAfter(char c)
{
if (IsAtEnd)
throw new InvalidOperationException("Reached end of line");
while (line[current] != c && current < line.Length)
current++;
while (line[current] == c && current < line.Length)
current++;
}
public void SkipWhitespace()
{
if (IsAtEnd)
throw new InvalidOperationException("Reached end of line");
while (!Char.IsWhiteSpace(line[current]) && current < line.Length)
current++;
while (Char.IsWhiteSpace(line[current]) && current < line.Length)
current++;
}
public string ReadUntil(char separator)
{
if (IsAtEnd)
throw new InvalidOperationException("Reached end of line");
if (line[current] == separator)
current++;
var end = line.IndexOf(separator, current);
if (end == -1)
return null;
LastSubstring = line.Substring(current, end - current);
current = end;
return LastSubstring;
}
public string ReadUntilWhitespace()
{
if (IsAtEnd)
throw new InvalidOperationException("Reached end of line");
if (Char.IsWhiteSpace(line[current]))
SkipWhitespace();
int end = line.Length;
for (var i = current; i < end; i++)
{
if (Char.IsWhiteSpace(line[i]))
{
end = i;
break;
}
}
if (end == line.Length)
return null;
LastSubstring = line.Substring(current, end - current);
current = end;
return LastSubstring;
}
public string ReadChunk(char startChar, char endChar, bool excludeTerminators = true)
{
if (IsAtEnd)
throw new InvalidOperationException("Reached end of line");
var start = line.IndexOf(startChar);
var end = line.IndexOf(endChar, start);
LastSubstring = line.Substring(start + (excludeTerminators ? 1 : 0), end - start + (excludeTerminators ? -1 : 1));
current = end;
return LastSubstring;
}
public string ReadToEnd()
{
if (IsAtEnd)
throw new InvalidOperationException("Already at end");
LastSubstring = line.Substring(current);
current = line.Length;
return LastSubstring;
}
public bool IsAtEnd { get { return line != null ? line.Length == current : true; } }
public string LastSubstring { get; private set; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
namespace GitHub.Unity
{
static class StringExtensions
{
/// <summary>
/// Pretty much the same things as `String.Join` but used when appending to an already delimited string. If the values passed
/// in are empty, it does not prepend the delimeter. Otherwise, it prepends with the delimiter.
/// </summary>
/// <param name="separator">The separator character</param>
/// <param name="values">The set values to join</param>
public static string JoinForAppending(string separator, IEnumerable<string> values)
{
return values.Any()
? separator + String.Join(separator, values.ToArray())
: string.Empty;
}
public static string RemoveSurroundingQuotes(this string s)
{
Ensure.ArgumentNotNull(s, "string");
if (s.Length < 2)
return s;
var quoteCharacters = new[] { '"', '\'' };
char firstCharacter = s[0];
if (!quoteCharacters.Contains(firstCharacter))
return s;
if (firstCharacter != s[s.Length - 1])
return s;
return s.Substring(1, s.Length - 2);
}
public static string RightAfter(this string s, char search)
{
if (s == null) return null;
int lastIndex = s.IndexOf(search);
if (lastIndex < 0)
return null;
return s.Substring(lastIndex + 1);
}
public static string RightAfterLast(this string s, char search)
{
if (s == null) return null;
int lastIndex = s.LastIndexOf(search);
if (lastIndex < 0)
return null;
return s.Substring(lastIndex + 1);
}
public static string LeftBeforeLast(this string s, char search)
{
if (s == null) return null;
int lastIndex = s.LastIndexOf(search);
if (lastIndex < 0)
return null;
return s.Substring(0, lastIndex);
}
public static StringResult? NextChunk(this string s, int start, char search)
{
if (s == null) return null;
int index = s.IndexOf(search, start);
if (index < 0)
return null;
return new StringResult { Chunk = s.Substring(start, index - start), Start = start, End = index };
}
public static StringResult? NextChunk(this string s, int start, string search)
{
if (s == null) return null;
int index = s.IndexOf(search, start);
if (index < 0)
return null;
return new StringResult { Chunk = s.Substring(start, index - start), Start = start, End = index };
}
}
public struct StringResult
{
public string Chunk;
public int Start;
public int End;
}
}
using System;
using System.Text;
namespace GitHub.Unity
{
class BaseOutputProcessor : IOutputProcessor
{
public event Action<string> OnData;
public virtual void LineReceived(string line)
{
if (line == null)
{
return;
}
OnData.Invoke(line);
}
}
}
\ No newline at end of file
using System;
using System.Text.RegularExpressions;
namespace GitHub.Unity
{
class BranchListOutputProcessor : BaseOutputProcessor
{
private static readonly Regex TrackingBranchRegex = new Regex(@"\[[\w]+\/.*\]");
public event Action<GitBranch> OnBranch;
public override void LineReceived(string line)
{
base.LineReceived(line);
if (line == null || OnBranch == null)
return;
var proc = new LineParser(line);
if (proc.IsAtEnd)
return;
var active = proc.Matches('*');
proc.SkipWhitespace();
var detached = proc.Matches("(HEAD ");
var name = "detached";
if (detached)
{
proc.MoveToAfter(')');
}
else
{
name = proc.ReadUntilWhitespace();
}
proc.SkipWhitespace();
proc.ReadUntilWhitespace();
var tracking = proc.Matches(TrackingBranchRegex);
var trackingName = "";
if (tracking)
{
trackingName = proc.ReadChunk('[', ']');
}
var branch = new GitBranch(name, trackingName, active);
OnBranch(branch);
}
}
}
\ No newline at end of file
using System;
namespace GitHub.Unity
{
class DefaultEnvironment : IEnvironment
{
public string GetFolderPath(Environment.SpecialFolder folder)
{
return ExpandEnvironmentVariables(Environment.GetFolderPath(folder));
}
public string ExpandEnvironmentVariables(string name)
{
return Environment.ExpandEnvironmentVariables(name);
}
public string GetEnvironmentVariable(string variable)
{
return Environment.GetEnvironmentVariable(variable);
}
public string GetTempPath()
{
return System.IO.Path.GetTempPath();
}
public string UserProfilePath { get { return Environment.GetEnvironmentVariable("USERPROFILE"); } }
public string Path { get { return Environment.GetEnvironmentVariable("PATH"); } }
public string NewLine { get { return Environment.NewLine; } }
public string GitInstallPath { get; set; }
public bool IsWindows { get; set; }
}
}
\ No newline at end of file
using System.IO;
namespace GitHub.Unity
{
class FileSystem : IFileSystem
{
public bool FileExists(string filename)
{
return File.Exists(filename);
}
}
}
\ No newline at end of file
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
namespace GitHub.Unity
{
class GitEnvironment : IGitEnvironment
{
readonly IEnvironment environment;
public GitEnvironment()
{
environment = new DefaultEnvironment();
}
public GitEnvironment(IEnvironment env)
{
environment = env;
}
public void Configure(ProcessStartInfo psi, string workingDirectory)
{
Ensure.ArgumentNotNull(psi, "psi");
// We need to essentially fake up what git-cmd.bat does
string homeDir = environment.UserProfilePath;
var userPath = environment.Path;
var appPath = workingDirectory;
var gitPath = environment.GitInstallPath;
var gitLfsPath = environment.GitInstallPath;
// Paths to developer tools such as msbuild.exe
//var developerPaths = StringExtensions.JoinForAppending(";", developerEnvironment.GetPaths());
var developerPaths = "";
psi.EnvironmentVariables["github_shell"] = "true";
psi.EnvironmentVariables["git_install_root"] = gitPath; // todo: remove in favor of github_git
psi.EnvironmentVariables["github_git"] = gitPath;
psi.EnvironmentVariables["PLINK_PROTOCOL"] = "ssh";
psi.EnvironmentVariables["TERM"] = "msys";
if (environment.IsWindows)
{
psi.EnvironmentVariables["PATH"] = String.Format(CultureInfo.InvariantCulture, @"{0}\cmd;{0}\usr\bin;{0}\usr\share\git-tfs;{1};{2};{3}{4}", gitPath, appPath, gitLfsPath, userPath, developerPaths);
}
else
{
psi.EnvironmentVariables["PATH"] = String.Format(CultureInfo.InvariantCulture, @"{0}:{1}:{2}:{3}{4}", gitPath, appPath, gitLfsPath, userPath, developerPaths);
}
psi.EnvironmentVariables["GIT_EXEC_PATH"] = gitPath;
psi.EnvironmentVariables["HOME"] = homeDir;
psi.EnvironmentVariables["TMP"] = psi.EnvironmentVariables["TEMP"] = environment.GetTempPath();
psi.EnvironmentVariables["EDITOR"] = environment.GetEnvironmentVariable("EDITOR");
var httpProxy = environment.GetEnvironmentVariable("HTTP_PROXY");
if (!String.IsNullOrEmpty(httpProxy))
psi.EnvironmentVariables["HTTP_PROXY"] = httpProxy;
var httpsProxy = environment.GetEnvironmentVariable("HTTPS_PROXY");
if (!String.IsNullOrEmpty(httpsProxy))
psi.EnvironmentVariables["HTTPS_PROXY"] = httpsProxy;
//var existingSshAgentProcess = sshAgentBridge.GetRunningSshAgentInfo();
//if (existingSshAgentProcess != null)
//{
// psi.EnvironmentVariables["SSH_AGENT_PID"] = existingSshAgentProcess.ProcessId;
// psi.EnvironmentVariables["SSH_AUTH_SOCK"] = existingSshAgentProcess.AuthSocket;
//}
bool internalUseOnly = false;
if (internalUseOnly)
{
psi.EnvironmentVariables["GIT_PAGER"] = "cat";
psi.EnvironmentVariables["LC_ALL"] = "C";
psi.EnvironmentVariables["GIT_ASKPASS"] = "true";
psi.EnvironmentVariables["DISPLAY"] = "localhost:1";
psi.EnvironmentVariables["SSH_ASKPASS"] = "true";
psi.EnvironmentVariables["GIT_SSH"] = "ssh-noprompt";
psi.StandardOutputEncoding = Encoding.UTF8;
psi.StandardErrorEncoding = Encoding.UTF8;
}
psi.WorkingDirectory = workingDirectory;
}
public IEnvironment Environment { get { return environment; } }
}
}
\ No newline at end of file
namespace GitHub.Unity
{
interface IEnvironment
{
string ExpandEnvironmentVariables(string name);
string GetEnvironmentVariable(string v);
string GetTempPath();
string Path { get; }
string UserProfilePath { get; }
string NewLine { get; }
string GitInstallPath { get; set; }
bool IsWindows { get; set; }
}
}
\ No newline at end of file
namespace GitHub.Unity
{
interface IFileSystem
{
bool FileExists(string filename);
}
}
\ No newline at end of file
using System.Diagnostics;
namespace GitHub.Unity
{
interface IGitEnvironment
{
void Configure(ProcessStartInfo psi, string workingDirectory);
IEnvironment Environment { get; }
}
}
\ No newline at end of file
namespace GitHub.Unity
{
interface IOutputProcessor
{
void LineReceived(string line);
}
}
\ No newline at end of file
using System;
namespace GitHub.Unity
{
interface IProcess
{
event Action<string> OnOutputData;
event Action<string> OnErrorData;
void Run();
bool WaitForExit(int milliseconds);
void WaitForExit();
void Close();
void Kill();
int Id { get; }
bool HasExited { get; }
event Action<IProcess> OnExit;
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace GitHub.Unity
{
class ProcessManager
{
readonly IGitEnvironment gitEnvironment;
readonly static IFileSystem fs = new FileSystem();
private static ProcessManager instance;
public static ProcessManager Instance
{
get
{
if (instance == null)
instance = new ProcessManager();
return instance;
}
set
{
instance = value;
}
}
public ProcessManager()
{
gitEnvironment = new GitEnvironment();
}
public ProcessManager(IGitEnvironment gitEnvironment)
{
this.gitEnvironment = gitEnvironment;
}
public IProcess Configure(string executableFileName, string arguments, string workingDirectory)
{
UnityEngine.Debug.Log("Configuring process " + executableFileName + " " + arguments + " " + workingDirectory);
var startInfo = new ProcessStartInfo(executableFileName, arguments)
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
gitEnvironment.Configure(startInfo, workingDirectory);
startInfo.FileName = FindExecutableInPath(executableFileName, startInfo.EnvironmentVariables["PATH"]) ?? executableFileName;
return new ProcessWrapper(startInfo);
}
public IProcess Reconnect(int pid)
{
UnityEngine.Debug.Log("Reconnecting process " + pid + " (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
var p = Process.GetProcessById(pid);
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
return new ProcessWrapper(p.StartInfo);
}
private string FindExecutableInPath(string executable, string path = null)
{
Ensure.ArgumentNotNullOrEmpty(executable, "executable");
if (Path.IsPathRooted(executable)) return executable;
path = path ?? gitEnvironment.Environment.GetEnvironmentVariable("PATH");
var executablePath = path.Split(Path.PathSeparator)
.Select(directory =>
{
try
{
var unquoted = directory.RemoveSurroundingQuotes();
var expanded = gitEnvironment.Environment.ExpandEnvironmentVariables(unquoted);
return Path.Combine(expanded, executable);
}
catch (Exception e)
{
UnityEngine.Debug.LogErrorFormat("Error while looking for {0} in {1}\n{2}", executable, directory, e);
return null;
}
})
.Where(x => x != null)
.FirstOrDefault(x => fs.FileExists(x));
return executablePath;
}
}
}
namespace GitHub.Unity
{
class ProcessOutputManager
{
public ProcessOutputManager(IProcess process, IOutputProcessor processor)
{
process.OnOutputData += processor.LineReceived;
process.OnErrorData += ProcessError;
}
private void ProcessError(string data)
{
}
}
}
\ No newline at end of file
using System;
using System.Diagnostics;
namespace GitHub.Unity
{
public static class ActionExtensions
{
public static void Invoke(this Action action)
{
if (action != null)
action();
}
public static void Invoke<T>(this Action<T> action, T obj)
{
if (action != null)
action(obj);
}
}
class ProcessWrapper : IProcess
{
public event Action<string> OnOutputData;
public event Action<string> OnErrorData;
public event Action<IProcess> OnExit;
private Process process;
public ProcessWrapper(ProcessStartInfo psi)
{
process = new Process { StartInfo = psi, EnableRaisingEvents = true };
process.OutputDataReceived += (s, e) =>
{
UnityEngine.Debug.Log("Data " + e.Data + " exit?" + process.HasExited + " (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
OnOutputData.Invoke(e.Data);
};
process.ErrorDataReceived += (s, e) =>
{
UnityEngine.Debug.Log("Error (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
OnErrorData.Invoke(e.Data);
if (process.HasExited)
{
OnExit.Invoke(this);
}
};
process.Exited += (s, e) =>
{
UnityEngine.Debug.Log("Exit (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
OnExit.Invoke(this);
};
}
public void Run()
{
UnityEngine.Debug.Log("Running process (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
public bool WaitForExit(int milliseconds)
{
UnityEngine.Debug.Log("Waiting " + milliseconds + " (" + System.Threading.Thread.CurrentThread.ManagedThreadId + ")");
// Workaround for a bug in which some data may still be processed AFTER this method returns true, thus losing the data.
// http://connect.microsoft.com/VisualStudio/feedback/details/272125/waitforexit-and-waitforexit-int32-provide-different-and-undocumented-implementations
bool waitSucceeded = process.WaitForExit(milliseconds);
if (waitSucceeded)
{
process.WaitForExit();
}
return waitSucceeded;
}
public void WaitForExit()
{
process.WaitForExit();
}
public void Close()
{
process.Close();
}
public void Kill()
{
process.Kill();
}
void OnDataReceived(DataReceivedEventHandler handler, DataReceivedEventArgs e)
{
handler.Invoke(this, e);
}
public int Id { get { return process.Id; } }
public bool HasExited { get { return process.HasExited; } }
}
}
\ No newline at end of file
......@@ -37,4 +37,4 @@ using System.Runtime.InteropServices;
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: InternalsVisibleTo("IOTests", AllInternalsVisible = true)]
......@@ -6,11 +6,16 @@ namespace GitHub.Unity
{
event Action<GitStatus> statusUpdated;
public static StatusService Instance { get; private set; }
public static void Initialize()
private static StatusService instance;
public static StatusService Instance
{
Instance = new StatusService();
get
{
if (instance == null)
instance = new StatusService();
return instance;
}
set { instance = value; }
}
public static void Shutdown()
......@@ -35,7 +40,7 @@ namespace GitHub.Unity
private void InternalInvoke(GitStatus status)
{
statusUpdated?.Invoke(status);
statusUpdated.Invoke(status);
}
}
}
......@@ -9,7 +9,7 @@ namespace GitHub.Unity
private string arguments = "";
private GitAddTask(IEnumerable<string> files, Action onSuccess = null, Action onFailure = null)
: base(str => onSuccess?.Invoke(), onFailure)
: base(str => onSuccess.Invoke(), onFailure)
{
arguments = "add ";
arguments += " -- ";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment