Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Krüger <mikkrg@microsoft.com>2019-08-26 09:38:01 +0300
committerMike Krüger <mikkrg@microsoft.com>2019-09-20 11:56:37 +0300
commita813dd32130af3759488cc74391c148133f8845a (patch)
tree87adc524be548418c465121816aee39344470957 /main/src/addins
parentfda4e5644b56ced4c713d16e3ed4ea28bfeaa2c7 (diff)
[VersionControl] Implemented git client library.
This library uses the git command line client instead of libgit2 to perform git tasks.
Diffstat (limited to 'main/src/addins')
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/ArgumentHandler.cs114
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/GitAskPass.cs65
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/MonoDevelop.VersionControl.Git.AskPass.csproj17
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/StreamStringReadWriter.cs70
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.AskPass/GitAskPassTests.cs131
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests.csproj61
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitProcessTests.cs86
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitVersionTests.cs53
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v1_StatusTests.cs73
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v2_StatusTests.cs128
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Properties/AssemblyInfo.cs51
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/packages.config4
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary.csproj66
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/AbstractGitCallbackHandler.cs42
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/CloneCallbackHandler.cs51
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/GitClone.cs71
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandler.cs55
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandlerDecorator.cs71
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCloneOptions.cs41
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCredentials.cs41
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitException.cs42
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProcess.cs266
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProgressEventArgs.cs42
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitResult.cs41
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/ProgressParser.cs52
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitFileState.cs82
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitRenameOrCopyScore.cs42
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatus.cs67
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V1_Handler.cs69
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V2_Handler.cs214
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.cs167
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCode.cs58
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitSubmoduleState.cs39
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Version/GitVersion.cs70
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/Properties/AssemblyInfo.cs51
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj5
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs257
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs3
-rw-r--r--main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/CheckoutCommand.cs4
39 files changed, 2731 insertions, 131 deletions
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/ArgumentHandler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/ArgumentHandler.cs
new file mode 100644
index 0000000000..4bc116434e
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/ArgumentHandler.cs
@@ -0,0 +1,114 @@
+//
+// ArgumentHandler.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace GitAskPass
+{
+ class ArgumentHandler
+ {
+ static Regex passPhraseRegex = new Regex("Enter passphrase for key \\'(.*)\\':\\s?", RegexOptions.Compiled);
+ static Regex passwordPrompt = new Regex("(.*)\\'s password:\\s?", RegexOptions.Compiled);
+
+ internal static bool Handle (string command, StreamStringReadWriter writer) =>
+ HandleUsernameRequest (command, writer) ||
+ HandlePasswordRequest (command, writer) ||
+ HandleConnecting (command, writer) ||
+ HandleSSHPassphrase (command, writer) ||
+ HandleSSHPassword (command, writer) ||
+ Error (command, writer);
+
+ static bool Error (string command, StreamStringReadWriter writer)
+ {
+ writer.WriteLine ("Error");
+ writer.WriteLine (command);
+ return false;
+ }
+
+ static bool HandleSSHPassword (string command, StreamStringReadWriter writer)
+ {
+ var match = passwordPrompt.Match (command);
+ if (match.Success) {
+ writer.WriteLine ("SSHPassword");
+ writer.WriteLine (match.Groups [1].Value);
+ return true;
+ }
+ return false;
+ }
+
+ static bool HandleSSHPassphrase (string command, StreamStringReadWriter writer)
+ {
+ var match = passPhraseRegex.Match (command);
+ if (match.Success) {
+ writer.WriteLine ("SSHPassPhrase");
+ writer.WriteLine (match.Groups [1].Value);
+ return true;
+ }
+ return false;
+ }
+
+ static bool HandleConnecting (string command, StreamStringReadWriter writer)
+ {
+ if (command.Contains ("Are you sure you want to continue connecting")) {
+ writer.WriteLine ("Continue connecting");
+ return true;
+ }
+ return false;
+ }
+
+ static bool HandleUsernameRequest (string command, StreamStringReadWriter writer)
+ {
+ if (command.StartsWith ("Username", StringComparison.InvariantCultureIgnoreCase)) {
+ var url = ParseUrl (command);
+ writer.WriteLine ("Username");
+ writer.WriteLine (url);
+ return true;
+ }
+ return false;
+ }
+
+ static bool HandlePasswordRequest (string command, StreamStringReadWriter writer)
+ {
+ if (command.StartsWith ("Password", StringComparison.InvariantCultureIgnoreCase)) {
+ var url = ParseUrl (command);
+ writer.WriteLine ("Password");
+ writer.WriteLine (url);
+ return true;
+ }
+ return false;
+ }
+
+ static string ParseUrl (string command)
+ {
+ var idx1 = command.IndexOf ('\'');
+ var idx2 = command.LastIndexOf ('\'');
+ var url = idx1 >= 0 && idx2 >= 0 ? command.Substring (idx1 + 1, idx2 - idx1 - 1) : "unknown";
+ return url;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/GitAskPass.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/GitAskPass.cs
new file mode 100644
index 0000000000..422aa5b69c
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/GitAskPass.cs
@@ -0,0 +1,65 @@
+//
+// GitAskPass.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.IO.Pipes;
+using System.Security.Principal;
+
+namespace GitAskPass
+{
+ class GitAskPass
+ {
+ public static int Main (string [] args)
+ {
+ if (args.Length < 1) {
+ Console.WriteLine ("No arguments specified.");
+ return 1;
+ }
+
+ string pipe = Environment.GetEnvironmentVariable ("MONODEVELOP_GIT_ASKPASS_PIPE");
+ if (string.IsNullOrEmpty (pipe)) {
+ Console.WriteLine ("No arguments specified.");
+ return 1;
+ }
+ try {
+ using (var client = new NamedPipeClientStream (".", pipe, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation)) {
+ client.Connect ();
+ var writer = new StreamStringReadWriter (client);
+ if (!ArgumentHandler.Handle (args [0], writer)) {
+ Console.WriteLine ("can't handle : " + args [0]);
+ return 2;
+ }
+ Console.WriteLine (writer.ReadLine ());
+ }
+ } catch (Exception e) {
+ Console.WriteLine (e.Message);
+ return -1;
+ }
+ return 0;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/MonoDevelop.VersionControl.Git.AskPass.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/MonoDevelop.VersionControl.Git.AskPass.csproj
new file mode 100644
index 0000000000..05b402d08d
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/MonoDevelop.VersionControl.Git.AskPass.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ <AssemblyName>GitAskPass</AssemblyName>
+ </PropertyGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <OutputPath>..\..\..\..\build\AddIns\VersionControl</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <OutputPath>..\..\..\..\build\AddIns\VersionControl</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ </PropertyGroup>
+</Project>
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/StreamStringReadWriter.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/StreamStringReadWriter.cs
new file mode 100644
index 0000000000..f92763ce23
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.AskPass/StreamStringReadWriter.cs
@@ -0,0 +1,70 @@
+//
+// StreamStringReadWriter.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+using System.Text;
+
+namespace GitAskPass
+{
+ sealed class StreamStringReadWriter
+ {
+ readonly Stream baseStream;
+
+ public StreamStringReadWriter (Stream baseStream)
+ {
+ this.baseStream = baseStream ?? throw new ArgumentNullException (nameof (baseStream));
+ }
+
+ public string ReadLine ()
+ {
+ int len = 0;
+ for (int i = 0; i < 4; i++)
+ len |= (int)(baseStream.ReadByte () << (8 * i));
+
+ var stringBuffer = new byte [len];
+ baseStream.Read (stringBuffer, 0, len);
+
+ return Encoding.UTF8.GetString (stringBuffer);
+ }
+
+ public void WriteLine (string value)
+ {
+ if (value is null)
+ throw new ArgumentNullException (nameof (value));
+ var buffer = Encoding.UTF8.GetBytes (value);
+ if (buffer.LongLength > int.MaxValue)
+ throw new InvalidOperationException ("string too long : " + buffer.LongLength);
+ int len = buffer.Length;
+ for (int i = 0; i < 4; i++) {
+ baseStream.WriteByte ((byte)(len & 255));
+ len >>= 8;
+ }
+ baseStream.Write (buffer, 0, buffer.Length);
+ baseStream.Flush ();
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.AskPass/GitAskPassTests.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.AskPass/GitAskPassTests.cs
new file mode 100644
index 0000000000..f0211cd6ee
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.AskPass/GitAskPassTests.cs
@@ -0,0 +1,131 @@
+//
+// GitAskPassTests.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using NUnit.Framework;
+using GitAskPass;
+using System.IO;
+using System.Diagnostics;
+using MonoDevelop.VersionControl.Git.ClientLibrary;
+using System.Threading;
+using System.IO.Pipes;
+
+namespace MonoDevelop.VersionControl.Git.AskPass
+{
+ [TestFixture]
+ public class GitAskPassTests
+ {
+ string Start (string argument, Action<StreamStringReadWriter> callback, int exitCode = 0)
+ {
+ var passApp = Path.Combine (Path.GetDirectoryName (typeof (GitProcess).Assembly.Location), "../AddIns/VersionControl/GitAskPass");
+ var startInfo = new ProcessStartInfo (passApp);
+ startInfo.RedirectStandardOutput = true;
+ startInfo.UseShellExecute = false;
+ startInfo.Arguments = "\"" + argument + "\"";
+ Console.WriteLine (startInfo.Arguments);
+ var pipe = "testPipe" + Thread.CurrentThread.ManagedThreadId;
+ startInfo.EnvironmentVariables.Add ("MONODEVELOP_GIT_ASKPASS_PIPE", pipe);
+ using (var server = new NamedPipeServerStream (pipe)) {
+ server.BeginWaitForConnection (stream => {
+ callback (new StreamStringReadWriter (server));
+ }, server);
+ if (!File.Exists (passApp))
+ Assert.Fail ("GitAskPass not found " + passApp);
+ var process = Process.Start (startInfo);
+ process.WaitForExit (5000);
+ server.Close ();
+
+ Assert.AreEqual (exitCode, process.ExitCode);
+ return process.StandardOutput.ReadToEnd ().TrimEnd ();
+ }
+ }
+
+ [Test]
+ public void TestUsernameRequest ()
+ {
+ var result = Start ("Username for 'fooBar':", stream => {
+ Assert.AreEqual ("Username", stream.ReadLine ());
+ Assert.AreEqual ("fooBar", stream.ReadLine ());
+ stream.WriteLine ("test_user");
+ });
+ Assert.AreEqual ("test_user", result);
+ }
+
+ [Test]
+ public void TestPasswordRequest ()
+ {
+ var result = Start ("Password for 'fooBar':", stream => {
+ Assert.AreEqual ("Password", stream.ReadLine ());
+ Assert.AreEqual ("fooBar", stream.ReadLine ());
+ stream.WriteLine ("test_pass");
+ });
+ Assert.AreEqual ("test_pass", result);
+ }
+
+ [Test]
+ public void TestContinueConnectingMessage ()
+ {
+ var result = Start ("Are you sure you want to continue connecting (yes/no)?", stream => {
+ Assert.AreEqual ("Continue connecting", stream.ReadLine ());
+ stream.WriteLine ("yes");
+
+ });
+ Assert.AreEqual ("yes", result);
+ }
+
+ [Test]
+ public void TestSshPassword ()
+ {
+ var result = Start ("Foo Bar's password:", stream => {
+ Assert.AreEqual ("SSHPassword", stream.ReadLine ());
+ stream.WriteLine ("Foo Bar");
+
+ });
+ Assert.AreEqual ("Foo Bar", result);
+ }
+
+ [Test]
+ public void TestKeyPassphrase ()
+ {
+ var result = Start ("Enter passphrase for key 'FooBar':", stream => {
+ Assert.AreEqual ("SSHPassPhrase", stream.ReadLine ());
+ stream.WriteLine ("FooBar");
+
+ });
+ Assert.AreEqual ("FooBar", result);
+ }
+
+ [Test]
+ public void TestError ()
+ {
+ var result = Start ("Some gibberish text.", stream => {
+ Assert.AreEqual ("Error", stream.ReadLine ());
+ stream.WriteLine ("Some gibberish text.");
+ }, exitCode: 2);
+ }
+ }
+}
+
+
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests.csproj
new file mode 100644
index 0000000000..b949740733
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests.csproj
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="..\..\..\..\packages\NUnit.3.12.0\build\NUnit.props" Condition="Exists('..\..\..\..\packages\NUnit.3.12.0\build\NUnit.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{72811761-AD0E-475E-8B53-AF24F449AF8D}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>MonoDevelop.VersionControl.Git.ClientLibrary.Tests</RootNamespace>
+ <AssemblyName>MonoDevelop.VersionControl.Git.ClientLibrary.Tests</AssemblyName>
+ <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\..\..\build\tests</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\..\..\build\tests</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="nunit.framework">
+ <HintPath>..\..\..\..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary.Tests\GitProcessTests.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.AskPass\GitAskPassTests.cs" />
+ <Compile Include="..\MonoDevelop.VersionControl.Git.AskPass\ArgumentHandler.cs">
+ <Link>MonoDevelop.VersionControl.Git.AskPass\ArgumentHandler.cs</Link>
+ </Compile>
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary.Tests\GitVersionTests.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary.Tests\Git_v1_StatusTests.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary.Tests\Git_v2_StatusTests.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MonoDevelop.VersionControl.Git.ClientLibrary\MonoDevelop.VersionControl.Git.ClientLibrary.csproj">
+ <Project>{7FBB8B40-2B59-4C11-8C51-47C09B67A2B3}</Project>
+ <Name>MonoDevelop.VersionControl.Git.ClientLibrary</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="MonoDevelop.VersionControl.Git.ClientLibrary.Tests\" />
+ <Folder Include="MonoDevelop.VersionControl.Git.AskPass\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitProcessTests.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitProcessTests.cs
new file mode 100644
index 0000000000..b5998c7a1e
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitProcessTests.cs
@@ -0,0 +1,86 @@
+//
+// GitProcessTests.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using NUnit.Framework;
+using System.Threading.Tasks;
+using NUnit.Framework.Internal;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary.Tests
+{
+ [TestFixture]
+ public class GitProcessTests
+ {
+ [TestCase ("error: Error")]
+ [TestCase (" error: Error")]
+ public void IsErrorTests (string errorLine)
+ {
+ Assert.IsTrue (GitProcess.IsError (errorLine));
+ }
+
+ [TestCase ("")]
+ [TestCase (" ")]
+ [TestCase ("some line…")]
+ public void IsNoErrorTests (string errorLine)
+ {
+ Assert.IsFalse (GitProcess.IsError (errorLine));
+ }
+
+ [TestCase]
+ public void TestStartRootPathNullCheck ()
+ {
+ try {
+ new GitProcess ().StartAsync (null, new GitCallbackHandler (), false);
+ } catch (ArgumentNullException e) {
+ Assert.AreEqual ("arguments", e.ParamName);
+ return;
+ }
+ Assert.Fail ("No exception thrown.");
+ }
+
+ [TestCase]
+ public void TestGitCallbackHandlerNullCheck ()
+ {
+ try {
+ var args = new GitArguments (".");
+ args.AddArgument ("Foo");
+ new GitProcess ().StartAsync (args, null, false);
+ } catch (ArgumentNullException e) {
+ Assert.AreEqual ("callbackHandler", e.ParamName);
+ return;
+ }
+ Assert.Fail ("No exception thrown.");
+ }
+
+ [TestCase]
+ public async Task TestGitStartupFail ()
+ {
+ var args = new GitArguments (".");
+ args.AddArgument ("needs to fail");
+ var result = await new GitProcess ().StartAsync (args, new GitCallbackHandler (), false);
+ Assert.AreEqual (1, result.ExitCode);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitVersionTests.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitVersionTests.cs
new file mode 100644
index 0000000000..69ae34e7b8
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/GitVersionTests.cs
@@ -0,0 +1,53 @@
+//
+// GitVersionTests.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using NUnit.Framework;
+using System.Threading.Tasks;
+using NUnit.Framework.Internal;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary.Tests
+{
+ [TestFixture]
+ class GitVersionTests
+ {
+ [TestCase]
+ public async Task TestGitVersion ()
+ {
+ var version = await GitVersion.GetVersionAsync ();
+ Assert.IsTrue (version.Major != 0);
+ }
+
+ [TestCase]
+ public void TestGitVersionParse ()
+ {
+ var version = GitVersion.ParseVersionString ("git version 1.2.3");
+ Assert.AreEqual (new Version (1, 2, 3, 0), version);
+
+ version = GitVersion.ParseVersionString ("git version 1.2.3.4");
+ Assert.AreEqual (new Version (1, 2, 3, 4), version);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v1_StatusTests.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v1_StatusTests.cs
new file mode 100644
index 0000000000..6a6a1bdefd
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v1_StatusTests.cs
@@ -0,0 +1,73 @@
+//
+// Git_v1_StatusTests.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using NUnit.Framework;
+using System.Threading.Tasks;
+using NUnit.Framework.Internal;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary.Tests
+{
+ public class Git_v1_StatusTests
+ {
+ [TestCase]
+ public void ParsePathTests ()
+ {
+ string test = "AM test/path\0AM test2/path2\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v1);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ Assert.IsTrue (dict.ContainsKey ("test/path"));
+ Assert.IsTrue (dict.ContainsKey ("test2/path2"));
+ }
+
+ [TestCase]
+ public void ParsePathTests_noPaths ()
+ {
+ var test = "AM \0AM test2/path2\0";
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v1);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ Assert.IsTrue (dict.ContainsKey (""));
+ Assert.IsTrue (dict.ContainsKey ("test2/path2"));
+ }
+
+ [TestCase]
+ public void ParseOrigPathTests ()
+ {
+ string test = "AM test/path\0AM test3/path3 -> test2/path2\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v1);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ Assert.IsTrue (dict.ContainsKey ("test/path"));
+ Assert.IsTrue (dict.ContainsKey ("test2/path2"));
+ var state = dict ["test2/path2"];
+ Assert.AreEqual ("test3/path3", state.OriginalPath);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v2_StatusTests.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v2_StatusTests.cs
new file mode 100644
index 0000000000..2c211b340e
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Git_v2_StatusTests.cs
@@ -0,0 +1,128 @@
+//
+// Git_v2_StatusTests.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using NUnit.Framework;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary.Tests
+{
+ public class Git_v2_StatusTests
+ {
+ [TestCase]
+ public void ParseUntrackedItems ()
+ {
+ string test = "? /foo/bar.cs\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["/foo/bar.cs"];
+ Assert.AreEqual (GitStatusCode.Untracked, state.StageState);
+ }
+
+ [TestCase]
+ public void ParseIgnoredItems ()
+ {
+ string test = "! /foo/bar.cs\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["/foo/bar.cs"];
+ Assert.AreEqual (GitStatusCode.Ignored, state.StageState);
+ }
+
+ [TestCase]
+ public void ParseOrdinaryChanges ()
+ {
+ string test = "1 .M N... 100644 100700 100777 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 README.md\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["README.md"];
+ Assert.AreEqual (GitStatusCode.WorktreeModified, state.StageState);
+ Assert.AreEqual (GitSubmoduleState.NoSubmodule, state.SubmoduleState);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Head);
+ Assert.AreEqual (Convert.ToInt32 ("100700", 8), state.FileMode_Index);
+ Assert.AreEqual (Convert.ToInt32 ("100777", 8), state.FileMode_Worktree);
+ }
+
+ [TestCase]
+ public void ParseRenamedChanges ()
+ {
+ string test = "2 R. N... 100644 100644 100644 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 R100 README2.md\0README.md\01 .M N... 100644 100700 100777 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 foo.cs\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["README2.md"];
+ Assert.AreEqual (GitStatusCode.IndexRenamed, state.StageState);
+ Assert.AreEqual (GitSubmoduleState.NoSubmodule, state.SubmoduleState);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Head);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Index);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Worktree);
+ Assert.IsTrue (state.RenameOrCopyScore.IsRename);
+ Assert.AreEqual (100, state.RenameOrCopyScore.Score);
+ Assert.AreEqual ("README.md", state.OriginalPath);
+ Assert.IsTrue (dict.ContainsKey ("foo.cs"));
+ }
+
+ [TestCase]
+ public void ParseUnmergedEntries ()
+ {
+ string test = "u UU N... 100644 100645 100646 100647 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 599bc5aca51701a55c1c8f8586bcbdb8fd56c568 dc8f30622338a15b432458a52d39a9784dafc44f README.md\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["README.md"];
+ Assert.AreEqual (GitStatusCode.IndexUnmerged | GitStatusCode.WorktreeUnmerged, state.StageState);
+ Assert.AreEqual (GitSubmoduleState.NoSubmodule, state.SubmoduleState);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Stage1);
+ Assert.AreEqual (Convert.ToInt32 ("100645", 8), state.FileMode_Stage2);
+ Assert.AreEqual (Convert.ToInt32 ("100646", 8), state.FileMode_Stage3);
+ Assert.AreEqual (Convert.ToInt32 ("100647", 8), state.FileMode_Worktree);
+ }
+
+ [TestCase]
+ public void TestIgnoreUnknownHeaders ()
+ {
+ string test = "#Doesn't make sense\01 DT N... 100644 100700 100777 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 119b2af8dc31e93206df6b8c8a7f58b930ac0ea8 README.md\0";
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (StatusVersion.v2);
+ handler.OnOutput (test);
+ var dict = handler.FileList;
+ var state = dict ["README.md"];
+ Assert.AreEqual (GitStatusCode.IndexDeleted | GitStatusCode.WorktreeTypeChanged, state.StageState);
+ Assert.AreEqual (GitSubmoduleState.NoSubmodule, state.SubmoduleState);
+ Assert.AreEqual (Convert.ToInt32 ("100644", 8), state.FileMode_Head);
+ Assert.AreEqual (Convert.ToInt32 ("100700", 8), state.FileMode_Index);
+ Assert.AreEqual (Convert.ToInt32 ("100777", 8), state.FileMode_Worktree);
+ }
+
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Properties/AssemblyInfo.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..d89a08a0d7
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,51 @@
+//
+// AssemblyInfo.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle ("MonoDevelop.VersionControl.Git.ClientLibrary.Tests")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion ("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/packages.config b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/packages.config
new file mode 100644
index 0000000000..41573816f2
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary.Tests/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="NUnit" version="3.12.0" targetFramework="net472" />
+</packages> \ No newline at end of file
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary.csproj
new file mode 100644
index 0000000000..25de0877ad
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary.csproj
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{7FBB8B40-2B59-4C11-8C51-47C09B67A2B3}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <RootNamespace>MonoDevelop.VersionControl.Git.ClientLibrary</RootNamespace>
+ <AssemblyName>MonoDevelop.VersionControl.Git.ClientLibrary</AssemblyName>
+ <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\..\..\..\build\AddIns\VersionControl</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <Optimize>true</Optimize>
+ <OutputPath>..\..\..\..\build\AddIns\VersionControl</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitCallbackHandler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitCloneOptions.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitCredentials.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitProcess.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitProgressEventArgs.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitResult.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\ProgressParser.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\AbstractGitCallbackHandler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitCallbackHandlerDecorator.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\GitException.cs" />
+ <Compile Include="..\MonoDevelop.VersionControl.Git.AskPass\StreamStringReadWriter.cs">
+ <Link>MonoDevelop.VersionControl.Git.ClientLibrary\StreamStringReadWriter.cs</Link>
+ </Compile>
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Clone\GitClone.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Clone\CloneCallbackHandler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Version\GitVersion.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitStatus.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitStatusCallbackHandler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitFileState.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitStatusCallbackHandler.V1_Handler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitStatusCallbackHandler.V2_Handler.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitStatusCode.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitSubmoduleState.cs" />
+ <Compile Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\GitRenameOrCopyScore.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="MonoDevelop.VersionControl.Git.ClientLibrary\" />
+ <Folder Include="MonoDevelop.VersionControl.Git.ClientLibrary\Clone\" />
+ <Folder Include="MonoDevelop.VersionControl.Git.ClientLibrary\Version\" />
+ <Folder Include="MonoDevelop.VersionControl.Git.ClientLibrary\Status\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/AbstractGitCallbackHandler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/AbstractGitCallbackHandler.cs
new file mode 100644
index 0000000000..3638771e73
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/AbstractGitCallbackHandler.cs
@@ -0,0 +1,42 @@
+//
+// GitProcess.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public abstract class AbstractGitCallbackHandler
+ {
+ public abstract void OnOutput (string line);
+ public abstract void OnReportProgress (string operation, int percentage);
+
+ public abstract GitCredentials OnGetCredentials (string url);
+
+ public abstract string OnGetSSHPassword (string userName);
+ public abstract string OnGetSSHPassphrase (string key);
+ public abstract bool OnGetContinueConnecting ();
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/CloneCallbackHandler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/CloneCallbackHandler.cs
new file mode 100644
index 0000000000..cf3e7b6026
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/CloneCallbackHandler.cs
@@ -0,0 +1,51 @@
+//
+// CloneCallbackHandler.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ class CloneCallbackHandler : GitCallbackHandlerDecorator
+ {
+ public string CloneTarget { get; set; }
+
+ public CloneCallbackHandler (AbstractGitCallbackHandler callbackHandler) : base (callbackHandler)
+ {
+ }
+
+ public override void OnOutput (string line)
+ {
+ if (CloneTarget == null && line.StartsWith ("Cloning into", StringComparison.InvariantCultureIgnoreCase)) {
+ var idx1 = line.IndexOf ('\'');
+ var idx2 = line.LastIndexOf ('\'');
+ if (idx1 >= 0 && idx2 >= 0) {
+ CloneTarget = line.Substring (idx1 + 1, idx2 - idx1);
+ }
+ }
+
+ base.OnOutput (line);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/GitClone.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/GitClone.cs
new file mode 100644
index 0000000000..8ef7d8de3f
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Clone/GitClone.cs
@@ -0,0 +1,71 @@
+//
+// GitClone.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Threading.Tasks;
+using System.Text;
+using System.Threading;
+using System.Text.RegularExpressions;
+using System.IO;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public static class GitClone
+ {
+ public static Task<GitResult> CloneAsync (string sourceUrl, string outputDir, AbstractGitCallbackHandler callbacks, CancellationToken cancellationToken = default) => CloneAsync (sourceUrl, outputDir, callbacks, GitCloneOptions.Default, cancellationToken);
+
+ public static async Task<GitResult> CloneAsync (string sourceUrl, string outputDir, AbstractGitCallbackHandler callbacks, GitCloneOptions options, CancellationToken cancellationToken = default)
+ {
+ if (sourceUrl is null)
+ throw new ArgumentNullException (nameof (sourceUrl));
+ if (outputDir is null)
+ throw new ArgumentNullException (nameof (outputDir));
+ if (options is null)
+ throw new ArgumentNullException (nameof (options));
+ var arg = new GitArguments (outputDir);
+ arg.AddArgument ("clone");
+ arg.AddArgument ("-c credential.helper=");
+ arg.AddArgument ("-c core.quotepath=false");
+ arg.AddArgument ("-c log.showSignature=false ");
+ arg.AddArgument ("--progress");
+ if (options.RecurseSubmodules)
+ arg.AddArgument ("--recurse-submodules");
+ if (options.IsBare)
+ arg.AddArgument ("--bare");
+ if (!string.IsNullOrEmpty (options.BranchName)) {
+ arg.AddArgument ("-- branch");
+ arg.AddArgument (options.BranchName);
+ }
+ arg.EndOptions ();
+ arg.AddArgument (sourceUrl);
+ arg.AddArgument (outputDir);
+ var handler = new CloneCallbackHandler (callbacks);
+ var result = await new GitProcess ().StartAsync (arg, handler, true, cancellationToken);
+ result.Properties[GitCloneOptions.CloneTargetProperty] = handler.CloneTarget;
+ return result;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandler.cs
new file mode 100644
index 0000000000..72a62000d7
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandler.cs
@@ -0,0 +1,55 @@
+//
+// GitCallbackHandler.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public class GitCallbackHandler : AbstractGitCallbackHandler
+ {
+ public Func<string, GitCredentials> GetCredentialsHandler;
+
+ public override GitCredentials OnGetCredentials (string url) => GetCredentialsHandler (url);
+
+ public event EventHandler<string> Output;
+
+ public override void OnOutput (string line) => Output?.Invoke (this, line);
+
+ public event EventHandler<GitProgressEventArgs> Progress;
+
+ public override void OnReportProgress (string operation, int percentage) => Progress?.Invoke (this, new GitProgressEventArgs (operation, percentage));
+
+ public Func<string, string> GetSSHPassword;
+ public override string OnGetSSHPassword (string userName) => GetSSHPassword (userName);
+
+ public Func<string, string> GetSSHPassphrase;
+ public override string OnGetSSHPassphrase (string key) => GetSSHPassphrase (key);
+
+ public Func<bool> GetContinueConnecting;
+
+ public override bool OnGetContinueConnecting () => GetContinueConnecting ();
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandlerDecorator.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandlerDecorator.cs
new file mode 100644
index 0000000000..9315f22409
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCallbackHandlerDecorator.cs
@@ -0,0 +1,71 @@
+//
+// GitCallbackHandler.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Text;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ class GitCallbackHandlerDecorator : AbstractGitCallbackHandler
+ {
+ readonly AbstractGitCallbackHandler callbackHandler;
+
+ public GitCallbackHandlerDecorator (AbstractGitCallbackHandler callbackHandler)
+ {
+ this.callbackHandler = callbackHandler;
+ }
+
+ public override bool OnGetContinueConnecting () => callbackHandler?.OnGetContinueConnecting () == true;
+
+ public override GitCredentials OnGetCredentials (string url) => callbackHandler?.OnGetCredentials (url);
+
+ public override string OnGetSSHPassphrase (string key) => callbackHandler?.OnGetSSHPassword (key);
+
+ public override string OnGetSSHPassword (string userName) => callbackHandler?.OnGetSSHPassword (userName);
+
+ public override void OnOutput (string line) => callbackHandler?.OnOutput (line);
+
+ public override void OnReportProgress (string operation, int percentage) => callbackHandler?.OnReportProgress (operation, percentage);
+
+ }
+
+ class GitOutputTrackerCallbackHandler : GitCallbackHandlerDecorator
+ {
+ StringBuilder output = new StringBuilder ();
+
+ public string Output { get => output.ToString (); }
+
+ public GitOutputTrackerCallbackHandler (AbstractGitCallbackHandler callbackHandler = null) : base (callbackHandler)
+ {
+ }
+
+ public override void OnOutput (string line)
+ {
+ output.AppendLine (line);
+ base.OnOutput (line);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCloneOptions.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCloneOptions.cs
new file mode 100644
index 0000000000..4ea63fa94b
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCloneOptions.cs
@@ -0,0 +1,41 @@
+//
+// GitCloneOptions.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public sealed class GitCloneOptions
+ {
+ public static readonly string CloneTargetProperty = "CloneTarget";
+
+ public static readonly GitCloneOptions Default = new GitCloneOptions ();
+
+ public bool IsBare { get; set; }
+
+ public string BranchName { get; set; }
+
+ public bool RecurseSubmodules { get; set; } = true;
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCredentials.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCredentials.cs
new file mode 100644
index 0000000000..fae0075928
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitCredentials.cs
@@ -0,0 +1,41 @@
+//
+// GitCredentials.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public sealed class GitCredentials
+ {
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+
+ public GitCredentials (string username, string password)
+ {
+ Username = username;
+ Password = password;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitException.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitException.cs
new file mode 100644
index 0000000000..4b13586c05
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitException.cs
@@ -0,0 +1,42 @@
+//
+// GitException.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public class GitException : Exception
+ {
+ public GitException (string message) : base (message)
+ {
+ }
+ }
+
+ public class GitExternalException : Exception
+ {
+ public GitExternalException (string message) : base (message)
+ {
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProcess.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProcess.cs
new file mode 100644
index 0000000000..aec694bccb
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProcess.cs
@@ -0,0 +1,266 @@
+//
+// GitProcess.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Text;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Net;
+using System.Net.Sockets;
+using System.IO.Pipes;
+using System.Linq;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ sealed class GitArguments
+ {
+ public string GitRootPath { get; }
+
+ StringBuilder arguments = new StringBuilder ();
+
+ public string Arguments { get => arguments.ToString (); }
+
+ public bool TrackProgress { get; set; } = true;
+
+ public GitArguments (string rootPath)
+ {
+ if (rootPath is null)
+ throw new ArgumentNullException (nameof (rootPath));
+ if (!Directory.Exists (rootPath))
+ throw new DirectoryNotFoundException (rootPath);
+ this.GitRootPath = rootPath;
+ }
+
+ public void AddArgument (string argument)
+ {
+ if (arguments.Length > 0)
+ arguments.Append (' ');
+ arguments.Append (argument);
+ }
+
+ public void EndOptions ()
+ {
+ AddArgument ("--");
+ }
+ }
+
+ sealed class GitProcess
+ {
+ public readonly static string [] errorIndicatorTags = {
+ "warning:",
+ "error:",
+ "fatal:",
+ "remote: error",
+ "Cannot",
+ "Could not",
+ "Interactive rebase already started",
+ "refusing to pull",
+ "cannot rebase:",
+ "conflict",
+ "unable",
+ "The file will have its original",
+ "runnerw:"
+ };
+
+ static TaskFactory exclusiveOperationFactory;
+ static TaskFactory concurrentOperationFactory;
+
+ static GitProcess ()
+ {
+ var scheduler = new ConcurrentExclusiveSchedulerPair ();
+ exclusiveOperationFactory = new TaskFactory (scheduler.ExclusiveScheduler);
+ concurrentOperationFactory = new TaskFactory (scheduler.ConcurrentScheduler);
+ }
+
+ Process process;
+ GitCredentials credentials = null;
+ AbstractGitCallbackHandler callbacks;
+ NamedPipeServerStream server;
+
+ StringBuilder errorText = new StringBuilder ();
+ string pipe;
+
+ public GitProcess ()
+ {
+ pipe = "MD_GIT_Pipe_" + Process.GetCurrentProcess ().Id;
+ }
+
+ public bool IsRunning { get => process != null && !process.HasExited; }
+
+ public Task<GitResult> StartAsync (GitArguments arguments, AbstractGitCallbackHandler callbackHandler, bool askPass, CancellationToken cancellationToken = default)
+ {
+ if (arguments is null)
+ throw new ArgumentNullException (nameof (arguments));
+ if (callbackHandler is null)
+ throw new ArgumentNullException (nameof (callbackHandler));
+
+
+ return (askPass ? exclusiveOperationFactory : concurrentOperationFactory).StartNew (delegate {
+ try {
+ this.callbacks = callbackHandler;
+ var startInfo = new ProcessStartInfo ("git");
+ startInfo.WorkingDirectory = arguments.GitRootPath;
+ startInfo.RedirectStandardError = true;
+ startInfo.RedirectStandardOutput = true;
+ startInfo.UseShellExecute = false;
+ startInfo.Arguments = arguments.Arguments;
+
+ if (askPass) {
+ var passApp = Path.Combine (Path.GetDirectoryName (typeof (GitProcess).Assembly.Location), "GitAskPass");
+ startInfo.EnvironmentVariables.Add ("GIT_ASKPASS", passApp);
+ startInfo.EnvironmentVariables.Add ("SSH_ASKPASS", passApp);
+ startInfo.EnvironmentVariables.Add ("MONODEVELOP_GIT_ASKPASS_PIPE", pipe);
+ server = new NamedPipeServerStream (pipe);
+ }
+
+ process = Process.Start (startInfo);
+ process.OutputDataReceived += (sender, e) => {
+ if (cancellationToken.IsCancellationRequested) {
+ SafeKillProcess ();
+ cancellationToken.ThrowIfCancellationRequested ();
+ }
+ if (e?.Data == null)
+ return;
+ if (arguments.TrackProgress && ProgressParser.ParseProgress (e.Data, callbackHandler))
+ return;
+ callbackHandler.OnOutput (e.Data);
+ };
+ process.ErrorDataReceived += (sender, e) => {
+ if (cancellationToken.IsCancellationRequested) {
+ SafeKillProcess ();
+ cancellationToken.ThrowIfCancellationRequested ();
+ }
+ if (e?.Data == null)
+ return;
+ if (IsError (e.Data)) {
+ errorText.AppendLine (e.Data);
+ } else {
+ if (arguments.TrackProgress && ProgressParser.ParseProgress (e.Data, callbackHandler))
+ return;
+ callbackHandler.OnOutput (e.Data);
+ }
+ };
+ if (!process.Start ())
+ throw new InvalidOperationException ("Can't start git process.");
+ process.BeginOutputReadLine ();
+ process.BeginErrorReadLine ();
+ cancellationToken.Register (SafeKillProcess);
+ if (askPass)
+ server.BeginWaitForConnection (HandleAsyncCallback, server);
+ process.WaitForExit ();
+ } finally {
+ SafeKillProcess ();
+ }
+ return new GitResult { Success = process.ExitCode == 0, ExitCode = process.ExitCode, ErrorMessage = errorText.ToString () };
+ }, cancellationToken);
+ }
+
+ void SafeKillProcess ()
+ {
+ try {
+ if (!process.HasExited)
+ process.Kill ();
+ if (server != null) {
+ server.Dispose ();
+ server = null;
+ }
+ } catch { }
+ }
+
+ void HandleAsyncCallback (IAsyncResult result)
+ {
+ var ns = server;
+
+ var reader = new GitAskPass.StreamStringReadWriter (ns);
+ var request = reader.ReadLine ();
+ switch (request) {
+ case "Username":
+ var url = reader.ReadLine ();
+ if (GetCredentials (url)) {
+ reader.WriteLine (credentials.Username);
+ }
+ break;
+ case "Password":
+ url = reader.ReadLine ();
+ if (GetCredentials (url)) {
+ reader.WriteLine (credentials.Password);
+ }
+ break;
+ case "Continue connecting":
+ reader.WriteLine (callbacks.OnGetContinueConnecting () ? "yes" : "no");
+ break;
+ case "SSHPassPhrase":
+ string key = reader.ReadLine ();
+ reader.WriteLine (callbacks.OnGetSSHPassphrase (key));
+ break;
+ case "SSHPassword":
+ string userName = reader.ReadLine ();
+ reader.WriteLine (callbacks.OnGetSSHPassword (userName));
+ break;
+ case "Error":
+ throw new GitExternalException (reader.ReadLine ());
+ }
+ server.Close ();
+
+ server = new NamedPipeServerStream (pipe);
+ server.BeginWaitForConnection (HandleAsyncCallback, server);
+ }
+
+ bool GetCredentials (string url)
+ {
+ if (credentials == null) {
+ try {
+ credentials = callbacks.OnGetCredentials (url);
+ if (credentials == null)
+ SafeKillProcess ();
+ } catch {
+ credentials = null;
+ SafeKillProcess ();
+ }
+ }
+ return credentials != null;
+ }
+
+ public static bool IsError (string line)
+ {
+ int start = 0;
+ while (start + 1 < line.Length && char.IsWhiteSpace (line [start]))
+ start++;
+ if (start >= line.Length)
+ return false;
+ foreach (var tag in errorIndicatorTags) {
+ if (start + tag.Length >= line.Length)
+ continue;
+ if (line.IndexOf (tag, start, tag.Length, StringComparison.InvariantCultureIgnoreCase) == start)
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProgressEventArgs.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProgressEventArgs.cs
new file mode 100644
index 0000000000..64dbbf82c2
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitProgressEventArgs.cs
@@ -0,0 +1,42 @@
+//
+// GitProgressEventArgs.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public sealed class GitProgressEventArgs : EventArgs
+ {
+ public string Operation { get; }
+ public int Percentage { get; }
+
+ public GitProgressEventArgs (string operation, int percentage)
+ {
+ Operation = operation;
+ Percentage = percentage;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitResult.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitResult.cs
new file mode 100644
index 0000000000..27140d0b13
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/GitResult.cs
@@ -0,0 +1,41 @@
+//
+// GitResult.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System.Collections.Generic;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public sealed class GitResult
+ {
+ public bool Success { get; internal set; }
+
+ public int ExitCode { get; internal set; }
+
+ public string ErrorMessage { get; internal set; }
+
+ public Dictionary<string, string> Properties { get; } = new Dictionary<string, string> ();
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/ProgressParser.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/ProgressParser.cs
new file mode 100644
index 0000000000..278927d0d6
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/ProgressParser.cs
@@ -0,0 +1,52 @@
+//
+// ProgressParser.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Text.RegularExpressions;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ static class ProgressParser
+ {
+ static readonly Regex progressRegex = new Regex (".*((?:Receiving|Writing|Compressing|Counting) objects|Resolving deltas|Checking out files): +(\\d{1,3})%j*", RegexOptions.Compiled);
+
+ public static bool ParseProgress (string line, AbstractGitCallbackHandler callbackHandler)
+ {
+ if (line is null)
+ throw new ArgumentNullException (nameof (line));
+ if (callbackHandler is null)
+ throw new ArgumentNullException (nameof (callbackHandler));
+
+ var match = progressRegex.Match (line);
+ if (match.Success) {
+ callbackHandler.OnReportProgress (match.Groups [1].Value, int.Parse(match.Groups [2].Value));
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitFileState.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitFileState.cs
new file mode 100644
index 0000000000..f568036437
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitFileState.cs
@@ -0,0 +1,82 @@
+//
+// GitStatus.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+
+ public readonly struct GitFileState
+ {
+ // data
+ readonly uint stateFlags; // 20 bit data contains: RenameOrCopyScore (8 bit) GitSubmoduleState (4 bit) GitStageState (16 bit)
+ readonly ulong fileMode; // contains 64 bit data : 4 FileModes = [WorkTree] [Stage3] [Stage2/Index] [Stage1/Head]
+ readonly string originalPath;
+
+ // all entries:
+ public GitStatusCode StageState { get => (GitStatusCode)(stateFlags & 0xFFFF); }
+ public GitSubmoduleState SubmoduleState { get => (GitSubmoduleState)((stateFlags >> 16) & 0xF); }
+
+ // for ordinary entries:
+ public ushort FileMode_Head { get => (ushort)fileMode; }
+ public ushort FileMode_Index { get => (ushort)(fileMode >> 16); }
+ public ushort FileMode_Worktree { get => (ushort)(fileMode >> 48); }
+
+ // For renamed/copied Entries:
+ public string OriginalPath { get => originalPath; }
+ public GitRenameOrCopyScore RenameOrCopyScore { get => new GitRenameOrCopyScore((byte)(stateFlags >> 20)); }
+
+ // For unmerged entries:
+ public ushort FileMode_Stage1 { get => FileMode_Head; }
+ public ushort FileMode_Stage2 { get => FileMode_Index; }
+ public ushort FileMode_Stage3 { get => (ushort)(fileMode >> 32); }
+
+ GitFileState (GitStatusCode stageState, GitSubmoduleState submoduleState, ushort fileMode_Stage1, ushort fileMode_Stage2, ushort fileMode_Stage3, ushort fileMode_Worktree, string originalPath, byte renameOrCopyScore)
+ {
+ stateFlags = (uint)stageState | ((uint)((byte)submoduleState & 0xF) << 16) | ((uint)renameOrCopyScore << 20);
+ fileMode = fileMode_Stage1 | (ulong)fileMode_Stage2 << 16 | (ulong)fileMode_Stage3 << 32 | (ulong)fileMode_Worktree << 48;
+ this.originalPath = originalPath;
+ }
+
+ internal static GitFileState CreateSimpleState (GitStatusCode stageState)
+ {
+ return new GitFileState (stageState, GitSubmoduleState.NoSubmodule, 0, 0, 0, 0, null, 0);
+ }
+
+ internal static GitFileState CreateOrdinaryState (GitStatusCode stageState, GitSubmoduleState submoduleState, ushort fileMode_Head, ushort fileMode_Index, ushort fileMode_Worktree)
+ {
+ return new GitFileState (stageState, submoduleState, fileMode_Head, fileMode_Index, 0, fileMode_Worktree, null, 0);
+ }
+
+ internal static GitFileState CreateRenamedState (GitStatusCode stageState, GitSubmoduleState submoduleState, ushort fileMode_Head, ushort fileMode_Index, ushort fileMode_Worktree, string originalPath, byte renameOrCopyScore)
+ {
+ return new GitFileState (stageState, submoduleState, fileMode_Head, fileMode_Index, 0, fileMode_Worktree, originalPath, renameOrCopyScore);
+ }
+
+ internal static GitFileState CreateUnmergedState (GitStatusCode stageState, GitSubmoduleState submoduleState, ushort fileMode_Stage1, ushort fileMode_Stage2, ushort fileMode_Stage3, ushort fileMode_Worktree)
+ {
+ return new GitFileState (stageState, submoduleState, fileMode_Stage1, fileMode_Stage2, fileMode_Stage3, fileMode_Worktree, null, 0);
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitRenameOrCopyScore.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitRenameOrCopyScore.cs
new file mode 100644
index 0000000000..4ea356e0e1
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitRenameOrCopyScore.cs
@@ -0,0 +1,42 @@
+//
+// GitRenameOrCopyScore.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public readonly struct GitRenameOrCopyScore
+ {
+ readonly byte data;
+
+ public bool IsRename { get => (data & 0b1000_0000) != 0; }
+
+ public int Score { get => data & 0b0111_1111; }
+
+ internal GitRenameOrCopyScore (byte data)
+ {
+ this.data = data;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatus.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatus.cs
new file mode 100644
index 0000000000..1c6f0e9379
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatus.cs
@@ -0,0 +1,67 @@
+//
+// GitStatus.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Threading.Tasks;
+using System.Text;
+using System.Threading;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Collections.Generic;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+
+ public static class GitStatus
+ {
+ public static async Task<Dictionary<string, GitFileState>> GetStatusAsync (string repository, IEnumerable<string> paths = null, StatusVersion version = StatusVersion.v2, CancellationToken cancellationToken = default)
+ {
+ if (repository is null)
+ throw new ArgumentNullException (nameof (repository));
+
+ var handler = GitStatusCallbackHandler.CreateCallbackHandler (version);
+
+ var args = new GitArguments (repository);
+ args.AddArgument ("status");
+ args.AddArgument ("--ignored");
+ if (version == StatusVersion.v2) {
+ args.AddArgument ("--porcelain=v2");
+ } else {
+ args.AddArgument ("--porcelain");
+ }
+ args.AddArgument ("-z");
+ args.AddArgument ("--untracked-files");
+ args.EndOptions ();
+ args.TrackProgress = false; // not required
+ if (paths != null) {
+ foreach (var path in paths)
+ args.AddArgument (path);
+ }
+ var result = await new GitProcess ().StartAsync (args, handler, false, cancellationToken);
+
+ return handler.FileList;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V1_Handler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V1_Handler.cs
new file mode 100644
index 0000000000..35ad7fedda
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V1_Handler.cs
@@ -0,0 +1,69 @@
+//
+// GitStatus.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ partial class GitStatusCallbackHandler
+ {
+ class V1_Handler : StatusCallbackHandler
+ {
+ protected override bool TryParseState (string data, int start, ref int end, out GitFileState state, out string path)
+ {
+ var (sx, sy, origPath, path2) = ParsePath (data, start, end);
+ path = path2;
+ if (origPath == null) {
+ state = GitFileState.CreateSimpleState (GetState (sx, sy));
+ } else {
+ state = GitFileState.CreateRenamedState (GetState (sx, sy), GitSubmoduleState.NoSubmodule, 0, 0, 0, origPath, 0);
+ }
+ return true;
+ }
+
+ internal static (char sx, char sy, string origPath, string path) ParsePath (string text, int from, int to)
+ {
+ // format to parse is:
+ // XY PATH
+ // XY ORIG_PATH -> PATH
+
+ char sx = text [from];
+ char sy = text [from + 1];
+ string origPath = null;
+ string path = null;
+ const int headerLength = 3;
+ var arrowIdx = text.IndexOf (" -> ", from + headerLength, to - from - headerLength, StringComparison.Ordinal);
+ if (arrowIdx >= 0) {
+ origPath = text.Substring (from + headerLength, arrowIdx - from - headerLength);
+ const int arrowLength = 4; // " -> ".Length;
+ path = text.Substring (arrowIdx + arrowLength, to - arrowIdx - arrowLength);
+ } else {
+ path = text.Substring (from + headerLength, to - from - headerLength);
+ }
+ return (sx, sy, origPath, path);
+ }
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V2_Handler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V2_Handler.cs
new file mode 100644
index 0000000000..1503034102
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.V2_Handler.cs
@@ -0,0 +1,214 @@
+//
+// GitStatus.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.IO;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ partial class GitStatusCallbackHandler
+ {
+ class V2_Handler : StatusCallbackHandler
+ {
+ protected override bool TryParseState (string data, int start, ref int end, out GitFileState state, out string path)
+ {
+ /*
+Ordinary changed entries have the following format:
+ 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
+Renamed or copied entries have the following format:
+ 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
+Unmerged entries have the following format; the first character is a "u" to distinguish from ordinary changed entries.
+ u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+Untracked items have the following format:
+ ? <path>
+Ignored items have the following format:
+ ! <path>
+
+ * */
+ switch (data [start]) {
+ case '1': return TryParseOrdinaryEntry (data, start + 2, end, out state, out path);
+ case '2': return TryParseRenamedEntry (data, start + 2, ref end, out state, out path);
+ case 'u': return TryParseUnmergedEntry (data, start + 2, end, out state, out path);
+ case '?':
+ state = UntrackedState;
+ path = data.Substring (start + 2, end - 2 - start);
+ return true;
+
+ case '!':
+ state = IgnoredState;
+ path = data.Substring (start + 2, end - 2 - start);
+ return true;
+ case '#': // Header: Ignore for now
+ state = UntrackedState;
+ path = null;
+ return false;
+ }
+
+ state = UntrackedState;
+ path = null;
+
+ return false;
+ }
+
+ bool TryParseOrdinaryEntry (string data, int start, int end, out GitFileState state, out string path)
+ {
+ //<XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
+ var k = start;
+ char x = data [k++]; // <XY>
+ char y = data [k++];
+ k++; // skip space
+ char subIndicator = data [k++]; // <sub> A 4 character field describing the submodule state.
+ char subHasCommitChanged = data [k++];
+ char subHasTrackedChanges = data [k++];
+ char subHasUntrackedChanges = data [k++];
+ k++; // skip space
+ var fileModeHead = ParseOctalNumber (data, ref k); // <mH>
+ var fileModeIndex = ParseOctalNumber (data, ref k); // <mI>
+ var fileModeWorkTree = ParseOctalNumber (data, ref k); // <mW>
+
+ while (data [k] != ' ') k++; // <hH> skip octal file mode in WorkTree
+ k++; // skip space
+
+ while (data [k] != ' ') k++; // <hI> skip octal file mode in WorkTree
+ k++; // skip space
+
+ path = data.Substring (k, end - k);
+ state = GitFileState.CreateOrdinaryState (GetState (x, y), GetSubmoduleState (subIndicator, subHasCommitChanged, subHasTrackedChanges, subHasUntrackedChanges), fileModeHead, fileModeIndex, fileModeWorkTree);
+ return true;
+ }
+
+ bool TryParseRenamedEntry (string data, int start, ref int end, out GitFileState state, out string path)
+ { // 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
+ var k = start;
+ char x = data [k++]; // <XY>
+ char y = data [k++];
+ k++; // skip space
+ char subIndicator = data [k++]; // <sub> A 4 character field describing the submodule state.
+ char subHasCommitChanged = data [k++];
+ char subHasTrackedChanges = data [k++];
+ char subHasUntrackedChanges = data [k++];
+ k++; // skip space
+ var fileModeHead = ParseOctalNumber (data, ref k); // <mH>
+ var fileModeIndex = ParseOctalNumber (data, ref k); // <mI>
+ var fileModeWorkTree = ParseOctalNumber (data, ref k); // <mW>
+
+ while (k < end && data [k] != ' ') k++; // <hH> skip octal file mode in WorkTree
+ k++; // skip space
+
+ while (k < end && data [k] != ' ') k++; // <hI> skip octal file mode in WorkTree
+ k++; // skip space
+
+ byte renameOrCopyScore = (byte)(data [k++] == 'R' ? 0x80 : 0); // <X>
+ int percentage = 0;
+ while (k < end) { // <score>
+ char ch = data [k];
+ if (ch == ' ')
+ break;
+ percentage = percentage * 10 + (ch - '0');
+ k++;
+ }
+ k++; // skip space
+
+ path = data.Substring (k, end - k);
+ end++;
+ int next = data.IndexOf ('\u0000', end);
+
+ state = GitFileState.CreateRenamedState (
+ GetState (x, y),
+ GetSubmoduleState (subIndicator, subHasCommitChanged, subHasTrackedChanges, subHasUntrackedChanges),
+ fileModeHead, fileModeIndex, fileModeWorkTree,
+ data.Substring (end, next - end),
+ (byte)(renameOrCopyScore | (percentage & 0x7F))
+ );
+ end = next;
+ return true;
+ }
+
+
+ bool TryParseUnmergedEntry (string data, int start, int end, out GitFileState state, out string path)
+ {
+ //<xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
+ var k = start;
+ char x = data [k++]; // <XY>
+ char y = data [k++];
+ k++; // skip space
+ char subIndicator = data [k++]; // <sub> A 4 character field describing the submodule state.
+ char subHasCommitChanged = data [k++];
+ char subHasTrackedChanges = data [k++];
+ char subHasUntrackedChanges = data [k++];
+ k++; // skip space
+ var fileModeStage1 = ParseOctalNumber (data, ref k); // <m1>
+ var fileModeStage2 = ParseOctalNumber (data, ref k); // <m2>
+ var fileModeStage3 = ParseOctalNumber (data, ref k); // <m3>
+ var fileModeWorkTree = ParseOctalNumber (data, ref k); // <mW>
+
+ while (data [k] != ' ') k++; // <h1> skip object name in stage 1.
+ k++; // skip space
+
+ while (data [k] != ' ') k++; // <h2> skip object name in stage 2.
+ k++; // skip space
+
+ while (data [k] != ' ') k++; // <h3> skip object name in stage 3.
+ k++; // skip space
+
+ path = data.Substring (k, end - k);
+ state = GitFileState.CreateUnmergedState (GetState (x, y), GetSubmoduleState (subIndicator, subHasCommitChanged, subHasTrackedChanges, subHasUntrackedChanges), fileModeStage1, fileModeStage2, fileModeStage3, fileModeWorkTree);
+ return true;
+ }
+
+ static ushort ParseOctalNumber (string data, ref int k)
+ {
+ int result = 0;
+ while (k < data.Length) {
+ char ch = data [k];
+ if (ch == ' ') {
+ k++; // skip space
+ return (ushort)result;
+ }
+ result = result * 8 + (ch - '0');
+ k++;
+ }
+ return (ushort)result;
+ }
+
+ static GitSubmoduleState GetSubmoduleState (char subIndicator, char subHasCommitChanged, char subHasTrackedChanges, char subHasUntrackedChanges)
+ {
+ if (subIndicator == 'N')
+ return GitSubmoduleState.NoSubmodule;
+ if (subIndicator != 'S')
+ throw new InvalidDataException ("Sub module indicator " + subIndicator + " unknown.");
+ var result = GitSubmoduleState.IsSubmodule;
+ if (subHasCommitChanged == 'C')
+ result |= GitSubmoduleState.CommitChanged;
+ if (subHasTrackedChanges == 'M')
+ result |= GitSubmoduleState.HasTrackedChanged;
+ if (subHasUntrackedChanges == 'U')
+ result |= GitSubmoduleState.HasUntrackedChanged;
+ return result;
+ }
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.cs
new file mode 100644
index 0000000000..db55b9abb0
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCallbackHandler.cs
@@ -0,0 +1,167 @@
+//
+// GitStatus.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+using System.Collections.Generic;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public enum StatusVersion
+ {
+ v1,
+ v2
+ }
+
+ abstract class StatusCallbackHandler : GitCallbackHandler
+ {
+ protected readonly static GitFileState IgnoredState = GitFileState.CreateSimpleState (GitStatusCode.Ignored);
+ protected readonly static GitFileState UntrackedState = GitFileState.CreateSimpleState (GitStatusCode.Untracked);
+
+
+ Dictionary<string, GitFileState> fileList = new Dictionary<string, GitFileState> ();
+
+ public Dictionary<string, GitFileState> FileList {
+ get {
+ return fileList;
+ }
+ }
+
+ public sealed override void OnOutput (string line)
+ {
+ int i = 0;
+ while (i < line.Length) {
+ int next = line.IndexOf ('\u0000', i);
+ if (next < 0 || next >= line.Length)
+ break;
+ if (TryParseState (line, i, ref next, out var state, out var path)) {
+ fileList [path] = state;
+ }
+ i = next + 1;
+ }
+ }
+
+ protected abstract bool TryParseState (string data, int start, ref int end, out GitFileState state, out string path);
+
+
+ protected static GitStatusCode GetState (char x, char y)
+ {
+ var result = GitStatusCode.Unmodified;
+
+ switch (x) {
+ case ' ':
+ case '.': break;
+ case 'M':
+ result |= GitStatusCode.IndexModified;
+ break;
+ case 'A':
+ result |= GitStatusCode.IndexAdded;
+ break;
+ case 'D':
+ result |= GitStatusCode.IndexDeleted;
+ break;
+ case 'R':
+ result |= GitStatusCode.IndexRenamed;
+ break;
+ case 'C':
+ result |= GitStatusCode.IndexCopied;
+ break;
+ case 'U':
+ result |= GitStatusCode.IndexUnmerged;
+ break;
+ case 'T':
+ result |= GitStatusCode.IndexTypeChanged;
+ break;
+ case 'X':
+ result |= GitStatusCode.IndexUnknown;
+ break;
+ case '?':
+ switch (y) {
+ case '?': return GitStatusCode.Untracked;
+ default:
+ throw new InvalidOperationException ("Unknown y state: " + y + " x state: " + x);
+ }
+ case '!':
+ switch (y) {
+ case '!': return GitStatusCode.Ignored;
+ default:
+ throw new InvalidOperationException ("Unknown y state: " + y + " x state: " + x);
+ }
+ default:
+ throw new InvalidOperationException ("Unknown x state: " + x + " (y state: " + y + ")");
+ }
+
+ switch (y) {
+ case ' ':
+ case '.': break;
+ case 'M':
+ result |= GitStatusCode.WorktreeModified;
+ break;
+ case 'A':
+ result |= GitStatusCode.WorktreeAdded;
+ break;
+ case 'D':
+ result |= GitStatusCode.WorktreeDeleted;
+ break;
+ case 'R':
+ result |= GitStatusCode.WorktreeRenamed;
+ break;
+ case 'C':
+ result |= GitStatusCode.WorktreeCopied;
+ break;
+ case 'U':
+ result |= GitStatusCode.WorktreeUnmerged;
+ break;
+ case 'T':
+ result |= GitStatusCode.WorktreeTypeChanged;
+ break;
+ case 'X':
+ result |= GitStatusCode.WorktreeUnknown;
+ break;
+ case '?': // should've been handled above.
+ case '!':
+ throw new InvalidOperationException ("Invalid y state: " + y + " (x state: " + x +")");
+ default:
+ throw new InvalidOperationException ("Unknown y state: " + y + " (x state: " + x + ")");
+ }
+
+ return result;
+ }
+ }
+
+ partial class GitStatusCallbackHandler
+ {
+ public static StatusCallbackHandler CreateCallbackHandler (StatusVersion version)
+ {
+ switch (version) {
+ case StatusVersion.v1:
+ return new V1_Handler ();
+ case StatusVersion.v2:
+ return new V2_Handler ();
+ default:
+ throw new InvalidOperationException ("Git status :" + version + " unknown.");
+ }
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCode.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCode.cs
new file mode 100644
index 0000000000..5f0832a07c
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitStatusCode.cs
@@ -0,0 +1,58 @@
+//
+// GitStatusCode.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ [Flags]
+ public enum GitStatusCode : ushort
+ {// X Y
+ Unmodified = 0b_0000_0000_0000_0000,
+
+ INDEX_MASK = 0b_1111_1111_0000_0000,
+ IndexModified = 0b_0000_0001_0000_0000, // M
+ IndexAdded = 0b_0000_0010_0000_0000, // A
+ IndexDeleted = 0b_0000_0011_0000_0000, // D
+ IndexRenamed = 0b_0000_0100_0000_0000, // R
+ IndexCopied = 0b_0000_0101_0000_0000, // C
+ IndexUnmerged = 0b_0000_0110_0000_0000, // U
+ IndexTypeChanged = 0b_0000_0111_0000_0000, // T
+ IndexUnknown = 0b_0000_1111_0000_0000, // X (should never happen)
+
+ WORKTREE_MASK = 0b_0000_0000_1111_1111,
+ WorktreeModified = 0b_0000_0000_0000_0001,
+ WorktreeAdded = 0b_0000_0000_0000_0010,
+ WorktreeDeleted = 0b_0000_0000_0000_0011,
+ WorktreeRenamed = 0b_0000_0000_0000_0100,
+ WorktreeCopied = 0b_0000_0000_0000_0101,
+ WorktreeUnmerged = 0b_0000_0000_0000_0110,
+ WorktreeTypeChanged = 0b_0000_0000_0000_0111,
+ WorktreeUnknown = 0b_0000_0000_0000_1111,
+
+ Untracked = 0b_1111_1110_1111_1110,
+ Ignored = 0b_1111_1111_1111_1111
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitSubmoduleState.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitSubmoduleState.cs
new file mode 100644
index 0000000000..07b311a79c
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Status/GitSubmoduleState.cs
@@ -0,0 +1,39 @@
+//
+// GitSubmoduleState.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ [Flags]
+ public enum GitSubmoduleState : byte
+ {
+ NoSubmodule = 0b0000,
+ IsSubmodule = 0b0001,
+ CommitChanged = 0b0010,
+ HasTrackedChanged = 0b0100,
+ HasUntrackedChanged = 0b1000,
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Version/GitVersion.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Version/GitVersion.cs
new file mode 100644
index 0000000000..21110cc590
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/MonoDevelop.VersionControl.Git.ClientLibrary/Version/GitVersion.cs
@@ -0,0 +1,70 @@
+//
+// GitVersion.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Threading.Tasks;
+using System.Text;
+using System.Threading;
+using System.Text.RegularExpressions;
+using System.IO;
+
+namespace MonoDevelop.VersionControl.Git.ClientLibrary
+{
+ public static class GitVersion
+ {
+ public static async Task<Version> GetVersionAsync (CancellationToken cancellationToken = default)
+ {
+ var handler = new GitOutputTrackerCallbackHandler ();
+ var arguments = new GitArguments (".");
+ arguments.AddArgument ("--version");
+
+ var result = await new GitProcess ().StartAsync (arguments, handler, false, cancellationToken);
+
+ return ParseVersionString (handler.Output);
+ }
+
+ static readonly Regex versionRegex = new Regex ("git version (\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(.*)");
+
+ internal static Version ParseVersionString (string output)
+ {
+ var match = versionRegex.Match (output);
+ if (!match.Success)
+ throw new InvalidOperationException ("Can't parse : " + output);
+
+ int major = ParseGroup (match, 1);
+ int minor = ParseGroup (match, 2);
+ int build = ParseGroup (match, 3);
+ int revision = ParseGroup (match, 4);
+
+ return new Version (major, minor, build, revision);
+ }
+
+ static int ParseGroup (Match match, int grp)
+ {
+ return !string.IsNullOrEmpty (match.Groups[grp].Value) ? int.Parse (match.Groups[grp].Value) : 0;
+ }
+ }
+}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/Properties/AssemblyInfo.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..1ae98fecbd
--- /dev/null
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git.ClientLibrary/Properties/AssemblyInfo.cs
@@ -0,0 +1,51 @@
+//
+// AssemblyInfo.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2019 Microsoft Corporation. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle ("MonoDevelop.VersionControl.Git.ClientLibrary")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("Microsoft Corporation. All rights reserved.")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion ("1.0.*")]
+[assembly: InternalsVisibleTo("MonoDevelop.VersionControl.Git.ClientLibrary.Tests")]
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj
index 5b156373db..b83670b966 100644
--- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git.csproj
@@ -107,6 +107,11 @@
<Name>MonoDevelop.SourceEditor</Name>
<Private>False</Private>
</ProjectReference>
+ <ProjectReference Include="..\MonoDevelop.VersionControl.Git.ClientLibrary\MonoDevelop.VersionControl.Git.ClientLibrary.csproj">
+ <Project>{7FBB8B40-2B59-4C11-8C51-47C09B67A2B3}</Project>
+ <Name>MonoDevelop.VersionControl.Git.ClientLibrary</Name>
+ <Private>False</Private>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MonoDevelop.VersionControl.Git.addin.xml" />
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs
index 21bea9dd5f..f9d1aeb657 100644
--- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs
@@ -38,6 +38,7 @@ using LibGit2Sharp;
using MonoDevelop.Core;
using MonoDevelop.Core.Text;
using MonoDevelop.Ide;
+using MonoDevelop.VersionControl.Git.ClientLibrary;
using ProgressMonitor = MonoDevelop.Core.ProgressMonitor;
namespace MonoDevelop.VersionControl.Git
@@ -496,9 +497,13 @@ namespace MonoDevelop.VersionControl.Git
}
public override bool TryGetFileUpdateEventInfo (Repository rep, FilePath file, out FileUpdateEventInfo eventInfo)
{
- if (file.FileName == "index" && file.ParentDirectory.FileName == ".git") {
- eventInfo = FileUpdateEventInfo.UpdateRepository (rep);
- return true;
+ if (file.ParentDirectory.FileName == ".git") {
+ if (file.FileName == "index") {
+ eventInfo = FileUpdateEventInfo.UpdateRepository (rep);
+ return true;
+ }
+ eventInfo = null;
+ return false;
}
return base.TryGetFileUpdateEventInfo (rep, file, out eventInfo);
}
@@ -1014,43 +1019,22 @@ namespace MonoDevelop.VersionControl.Git
return versions.ToArray ();
}
-
- void GetFilesVersionInfoCore (LibGit2Sharp.Repository repo, GitRevision rev, List<FilePath> localPaths, List<VersionInfo> versions)
+
+ static async Task GetFilesVersionInfoCoreAsync (LibGit2Sharp.Repository repo, GitRevision rev, List<FilePath> localPaths, List<VersionInfo> versions)
{
- if (IsDisposed)
- return;
- AssertIsGitThread ();
- foreach (var localPath in localPaths) {
- if (!localPath.IsDirectory) {
- var file = repo.ToGitPath (localPath);
- var status = repo.RetrieveStatus (file);
- AddStatus (repo, rev, file, versions, status, null);
- }
+ var paths = localPaths.Where (f => !f.IsDirectory).Select (f => repo.ToGitPath (f)).ToArray ();
+ var result = await GitStatus.GetStatusAsync (repo.Info.WorkingDirectory, paths, version: StatusVersion.v1);
+ foreach (var file in paths) {
+ if (!result.TryGetValue (file, out var gitFileState)) {
+ AddStatus (repo, rev, file, versions, GitStatusCode.Unmodified, null);
+ } else
+ AddStatus (repo, rev, file, versions, gitFileState.StageState, null);
}
}
- static void AddStatus (LibGit2Sharp.Repository repo, GitRevision rev, string file, List<VersionInfo> versions, FileStatus status, string directoryPath)
+ static void AddStatus (LibGit2Sharp.Repository repo, GitRevision rev, string file, List<VersionInfo> versions, GitStatusCode status, string directoryPath)
{
- VersionStatus fstatus = VersionStatus.Versioned;
-
- if (status != FileStatus.Unaltered) {
- if ((status & FileStatus.NewInIndex) != 0)
- fstatus |= VersionStatus.ScheduledAdd;
- else if ((status & (FileStatus.DeletedFromIndex | FileStatus.DeletedFromWorkdir)) != 0)
- fstatus |= VersionStatus.ScheduledDelete;
- else if ((status & (FileStatus.TypeChangeInWorkdir | FileStatus.TypeChangeInIndex | FileStatus.ModifiedInWorkdir | FileStatus.ModifiedInIndex)) != 0)
- fstatus |= VersionStatus.Modified;
- else if ((status & (FileStatus.RenamedInIndex | FileStatus.RenamedInWorkdir)) != 0)
- fstatus |= VersionStatus.ScheduledReplace;
- else if ((status & (FileStatus.Nonexistent | FileStatus.NewInWorkdir)) != 0)
- fstatus = VersionStatus.Unversioned;
- else if ((status & FileStatus.Ignored) != 0)
- fstatus = VersionStatus.Ignored;
- }
-
- if (repo.Index.Conflicts [file] != null)
- fstatus = VersionStatus.Versioned | VersionStatus.Conflicted;
-
+ var fstatus = ConvertGitState (status);
var versionPath = repo.FromGitPath (file);
if (directoryPath != null && versionPath.ParentDirectory != directoryPath) {
return;
@@ -1065,20 +1049,50 @@ namespace MonoDevelop.VersionControl.Git
throw new InvalidOperationException ();
}
- void GetDirectoryVersionInfoCore (LibGit2Sharp.Repository repo, GitRevision rev, FilePath directory, List<VersionInfo> versions, bool recursive)
- {
- if (IsDisposed)
- return;
- AssertIsGitThread ();
- var relativePath = repo.ToGitPath (directory);
- var status = repo.RetrieveStatus (new StatusOptions {
- DisablePathSpecMatch = true,
- PathSpec = relativePath != "." ? new [] { relativePath } : new string[0],
- IncludeUnaltered = true,
- });
+ static VersionStatus ConvertGitState (GitStatusCode status)
+ {
+ switch (status) {
+ case GitStatusCode.Untracked:
+ return VersionStatus.Unversioned;
+ case GitStatusCode.Ignored:
+ return VersionStatus.Ignored;
+ default:
+ switch (status & GitStatusCode.INDEX_MASK) {
+ case GitStatusCode.IndexAdded:
+ return VersionStatus.Versioned | VersionStatus.ScheduledAdd;
+ case GitStatusCode.IndexDeleted:
+ return VersionStatus.Versioned | VersionStatus.ScheduledDelete;
+ case GitStatusCode.IndexTypeChanged:
+ case GitStatusCode.IndexModified:
+ return VersionStatus.Versioned | VersionStatus.Modified;
+ case GitStatusCode.IndexCopied:
+ return VersionStatus.Versioned | VersionStatus.ScheduledReplace;
+ case GitStatusCode.IndexUnmerged:
+ return VersionStatus.Versioned | VersionStatus.Conflicted;
+ }
+
+ switch (status & GitStatusCode.WORKTREE_MASK) {
+ case GitStatusCode.WorktreeAdded:
+ return VersionStatus.Versioned | VersionStatus.ScheduledAdd;
+ case GitStatusCode.WorktreeDeleted:
+ return VersionStatus.Versioned | VersionStatus.ScheduledDelete;
+ case GitStatusCode.WorktreeTypeChanged:
+ case GitStatusCode.WorktreeModified:
+ return VersionStatus.Versioned | VersionStatus.Modified;
+ case GitStatusCode.WorktreeCopied:
+ return VersionStatus.Versioned | VersionStatus.ScheduledReplace;
+ case GitStatusCode.WorktreeUnmerged:
+ return VersionStatus.Versioned | VersionStatus.Conflicted;
+ }
+ return VersionStatus.Versioned;
+ }
+ }
- foreach (var statusEntry in status) {
- AddStatus (repo, rev, statusEntry.FilePath, versions, statusEntry.State, recursive ? null : directory);
+ static async Task GetDirectoryVersionInfoCoreAsync (LibGit2Sharp.Repository repo, GitRevision rev, FilePath directory, List<VersionInfo> versions, bool recursive)
+ {
+ var result = await GitStatus.GetStatusAsync (repo.Info.WorkingDirectory, new [] { directory.ToString() } , version: StatusVersion.v1);
+ foreach (var file in result) {
+ AddStatus (repo, rev, file.Key, versions, file.Value.StageState, recursive ? null : directory);
}
}
@@ -1585,69 +1599,34 @@ namespace MonoDevelop.VersionControl.Git
protected override async Task OnCheckoutAsync (FilePath targetLocalPath, Revision rev, bool recurse, ProgressMonitor monitor)
{
- int transferProgress = 0;
- int checkoutProgress = 0;
-
try {
- monitor.BeginTask (GettextCatalog.GetString ("Cloning…"), 2);
- bool skipSubmodules = false;
- var innerTask = await RunOperationAsync (() => RetryUntilSuccessAsync (monitor, credType => {
- var options = new CloneOptions {
- CredentialsProvider = (url, userFromUrl, types) => {
- transferProgress = checkoutProgress = 0;
- return GitCredentials.TryGet (url, userFromUrl, types, credType);
- },
- RepositoryOperationStarting = ctx => {
- Runtime.RunInMainThread (() => {
- monitor.Log.WriteLine (GettextCatalog.GetString ("Checking out repository at '{0}'"), ctx.RepositoryPath);
- });
- return true;
- },
- OnTransferProgress = (tp) => OnTransferProgress (tp, monitor, ref transferProgress),
- OnCheckoutProgress = (path, completedSteps, totalSteps) => {
- OnCheckoutProgress (completedSteps, totalSteps, monitor, ref checkoutProgress);
- Runtime.RunInMainThread (() => {
- monitor.Log.WriteLine (GettextCatalog.GetString ("Checking out file '{0}'"), path);
- });
- }
- };
+ monitor.BeginTask ("Cloning…", 100);
+ if (!Directory.Exists (targetLocalPath))
+ Directory.CreateDirectory (targetLocalPath);
+ var options = new GitCloneOptions { RecurseSubmodules = recurse };
+ var result = await GitClone.CloneAsync (Url, targetLocalPath, CreateCallbacks (monitor), options, monitor.CancellationToken);
+ if (result.Properties.TryGetValue (GitCloneOptions.CloneTargetProperty, out var rootPath)) {
+ RootPath = targetLocalPath.Combine (rootPath).CanonicalPath.ParentDirectory;
+ } else {
+ return;
+ }
- try {
- RootPath = LibGit2Sharp.Repository.Clone (Url, targetLocalPath, options);
- } catch (UserCancelledException) {
- return Task.CompletedTask;
- }
- var updateOptions = new SubmoduleUpdateOptions {
- Init = true,
- CredentialsProvider = options.CredentialsProvider,
- OnTransferProgress = options.OnTransferProgress,
- OnCheckoutProgress = options.OnCheckoutProgress,
- };
- monitor.Step (1);
- try {
- if (!skipSubmodules)
- RecursivelyCloneSubmodules (RootPath, updateOptions, monitor);
- } catch (Exception e) {
- LoggingService.LogError ("Cloning submodules failed", e);
- FileService.DeleteDirectory (RootPath);
- skipSubmodules = true;
- return Task.FromException (e);
- }
- return Task.CompletedTask;
- }), monitor.CancellationToken).ConfigureAwait (false);
+ if (monitor.CancellationToken.IsCancellationRequested)
+ return;
await innerTask.ConfigureAwait (false);
if (monitor.CancellationToken.IsCancellationRequested || RootPath.IsNull)
return;
- RootPath = RootPath.CanonicalPath.ParentDirectory;
-
- RootRepository = await CreateSafeRepositoryAsync (RootPath).ConfigureAwait (false);
+ RootRepository = new LibGit2Sharp.Repository (RootPath);
InitFileWatcher ();
- if (skipSubmodules) {
- MessageService.ShowError (GettextCatalog.GetString("Cloning submodules failed"), GettextCatalog.GetString ("Please use the command line client to init the submodules manually."));
- }
+ } catch (OperationCanceledException) {
+ return;
+ } catch (GitExternalException e) {
+ LoggingService.LogInternalError ("Error while running git", e);
+ MessageService.ShowError (GettextCatalog.GetString ("Error while running git"), e.Message);
+ return;
} catch (Exception e) {
LoggingService.LogInternalError ("Error while cloning repository " + rev + " recuse: " + recurse, e);
throw e;
@@ -1656,39 +1635,57 @@ namespace MonoDevelop.VersionControl.Git
}
}
- static void RecursivelyCloneSubmodules (string repoPath, SubmoduleUpdateOptions updateOptions, ProgressMonitor monitor)
+ GitCallbackHandler CreateCallbacks (ProgressMonitor monitor)
{
- var submodules = new List<string> ();
- using (var repo = new LibGit2Sharp.Repository (repoPath)) {
- // Iterate through the submodules (where the submodule is in the index),
- // and clone them.
- var submoduleArray = repo.Submodules.Where (sm => sm.RetrieveStatus ().HasFlag (SubmoduleStatus.InIndex)).ToArray ();
- monitor.BeginTask (GettextCatalog.GetString ("Cloning submodules…"), submoduleArray.Length);
- try {
- foreach (var sm in submoduleArray) {
- if (monitor.CancellationToken.IsCancellationRequested) {
- throw new UserCancelledException ("Recursive clone of submodules was cancelled.");
- }
+ var callbacks = new GitCallbackHandler {
+ GetCredentialsHandler = (url) => {
+ var cred = GitCredentials.TryGet (url, Environment.UserName, SupportedCredentialTypes.UsernamePassword, GitCredentialsType.Normal) as UsernamePasswordCredentials;
+ if (cred == null)
+ throw new InvalidOperationException ("Can't get username/password credentials.");
+ return new ClientLibrary.GitCredentials (cred.Username, cred.Password);
+ },
- Runtime.RunInMainThread (() => {
- monitor.Log.WriteLine (GettextCatalog.GetString ("Checking out submodule at '{0}'…", sm.Path));
- monitor.Step (1);
- });
- repo.Submodules.Update (sm.Name, updateOptions);
+ GetSSHPassword = (userName) => {
+ var cred = GitCredentials.TryGet (Url, userName, SupportedCredentialTypes.Ssh, GitCredentialsType.Normal) as SshUserKeyCredentials;
+ if (cred == null)
+ throw new InvalidOperationException ("Can't ssh password.");
+ return cred.Passphrase;
+ },
- submodules.Add (Path.Combine (repo.Info.WorkingDirectory, sm.Path));
- }
- } finally {
- monitor.EndTask ();
+ GetSSHPassphrase = (key) => {
+ var cred = GitCredentials.TryGet (Url, key, SupportedCredentialTypes.Ssh, GitCredentialsType.Normal) as SshUserKeyCredentials;
+ if (cred == null)
+ throw new InvalidOperationException ("Can't get ssh passphrase.");
+ return cred.Passphrase;
+ },
+
+ GetContinueConnecting = () => true
+ };
+
+ callbacks.Output += (sender, e) => {
+ Runtime.RunInMainThread (delegate {
+ monitor.Log.WriteLine (e);
+ });
+ };
+ string curTask = null;
+ int oldPercentage = 0;
+ callbacks.Progress += (sender, e) => {
+ if (curTask != e.Operation) {
+ if (curTask != null)
+ monitor.EndTask ();
+ monitor.BeginTask (e.Operation + "…", 100);
+ oldPercentage = 0;
+ curTask = e.Operation;
}
- }
+ monitor.Step (e.Percentage - oldPercentage);
+ oldPercentage = e.Percentage;
- // If we are continuing the recursive operation, then
- // recurse into nested submodules.
- // Check submodules to see if they have their own submodules.
- foreach (string path in submodules) {
- RecursivelyCloneSubmodules (path, updateOptions, monitor);
- }
+ if (e.Percentage == 100) {
+ monitor.EndTask ();
+ curTask = null;
+ }
+ };
+ return callbacks;
}
protected override async Task OnRevertAsync (FilePath [] localPaths, bool recurse, ProgressMonitor monitor)
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs
index afbafe0e84..3b0ab3acc7 100644
--- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs
@@ -990,6 +990,8 @@ namespace MonoDevelop.VersionControl.Views
void OnFileStatusChanged (object s, FileUpdateEventArgs args)
{
+ VersionControlService.FileStatusChanged -= OnFileStatusChanged;
+
try {
if (args.Any (f => f.FilePath == filepath || (filepath != null && !f.FilePath.IsNullOrEmpty && f.FilePath.IsChildPathOf (filepath) && f.IsDirectory))) {
StartUpdate ();
@@ -1000,6 +1002,7 @@ namespace MonoDevelop.VersionControl.Views
break;
}
UpdateControlStatus ();
+ VersionControlService.FileStatusChanged += OnFileStatusChanged;
} catch (Exception e) {
LoggingService.LogInternalError (e);
}
diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/CheckoutCommand.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/CheckoutCommand.cs
index 065c117d4b..79f67f5242 100644
--- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/CheckoutCommand.cs
+++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/CheckoutCommand.cs
@@ -51,7 +51,9 @@ namespace MonoDevelop.VersionControl
{
return new MonoDevelop.Core.ProgressMonitoring.AggregatedProgressMonitor (
base.CreateProgressMonitor (),
- new MonoDevelop.Ide.ProgressMonitoring.MessageDialogProgressMonitor (true, true, true, true)
+ new MonoDevelop.Ide.ProgressMonitoring.MessageDialogProgressMonitor (true, true, true, true) {
+ Style = Ide.ProgressMonitoring.MessageDialogProgressStyle.ShowCurrentTaskPercentage
+ }
);
}