public static async Task <string> Clean(string folder, CancellationToken cancellationToken,
                                                bool removeUntrackedDirectories = true)
        {
            if (!Directory.Exists(folder))
            {
                throw new ArgumentException($"Specified folder: \"{folder}\" doesn't exist");
            }

            var startInfo = new ProcessStartInfo(FindGit())
            {
                CreateNoWindow = true, WorkingDirectory = folder
            };

            startInfo.ArgumentList.Add("clean");
            startInfo.ArgumentList.Add("-f");

            if (removeUntrackedDirectories)
            {
                startInfo.ArgumentList.Add("-d");
            }

            var result = await ProcessRunHelpers.RunProcessAsync(startInfo, cancellationToken);

            if (result.ExitCode != 0)
            {
                throw new Exception(
                          $"Failed to clean repo, process exited with error: {result.FullOutput}");
            }

            return(result.Output);
        }
        public static async Task Fetch(string folder, bool all, CancellationToken cancellationToken)
        {
            var startInfo = PrepareToRunGit(folder, true);

            startInfo.ArgumentList.Add("fetch");

            if (all)
            {
                startInfo.ArgumentList.Add("--all");
            }

            var result = await ProcessRunHelpers.RunProcessAsync(startInfo, cancellationToken);

            if (result.ExitCode != 0)
            {
                throw new Exception(
                          $"Failed to fetch in repo, process exited with error: {result.FullOutput}");
            }
        }
        public static async Task Pull(string folder, bool skipLFS, CancellationToken cancellationToken,
                                      bool force = false)
        {
            var startInfo = PrepareToRunGit(folder, skipLFS);

            startInfo.ArgumentList.Add("pull");

            if (force)
            {
                startInfo.ArgumentList.Add("--force");
            }

            var result = await ProcessRunHelpers.RunProcessAsync(startInfo, cancellationToken);

            if (result.ExitCode != 0)
            {
                throw new Exception(
                          $"Failed to pull in repo, process exited with error: {result.FullOutput}");
            }
        }
        public static async Task EnsureRepoIsCloned(string repoURL, string folder, bool skipLFS,
                                                    CancellationToken cancellationToken)
        {
            var startInfo = new ProcessStartInfo(FindGit())
            {
                CreateNoWindow = true
            };

            if (skipLFS)
            {
                SetLFSSmudgeSkip(startInfo);
            }

            if (!Directory.Exists(folder))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(folder) ??
                                          throw new Exception("Could not get parent folder to put the repository in"));

                // Need to clone
                startInfo.ArgumentList.Add("clone");
                startInfo.ArgumentList.Add(repoURL);
                startInfo.ArgumentList.Add(folder);
            }
            else
            {
                // Just update remote
                startInfo.WorkingDirectory = folder;
                startInfo.ArgumentList.Add("remote");
                startInfo.ArgumentList.Add("set-url");
                startInfo.ArgumentList.Add("origin");
                startInfo.ArgumentList.Add(repoURL);
            }

            var result = await ProcessRunHelpers.RunProcessAsync(startInfo, cancellationToken);

            if (result.ExitCode != 0)
            {
                throw new Exception(
                          $"Failed to make sure repo is cloned, process exited with error: {result.FullOutput}");
            }
        }
        /// <summary>
        ///   Gets the current commit in a git repository in the long form
        /// </summary>
        /// <param name="folder">The repository folder</param>
        /// <param name="cancellationToken">Cancellation token for the git process</param>
        /// <param name="attempts">
        ///   How many times to attempt getting the commit. This seems to spuriously fail with 0 exit code and
        ///   no output so this parameter guards against that.
        /// </param>
        /// <returns>The current commit hash</returns>
        /// <exception cref="Exception">If getting the commit hash fails</exception>
        public static async Task <string> GetCurrentCommit(string folder, CancellationToken cancellationToken,
                                                           int attempts = 4)
        {
            int i = 0;

            while (true)
            {
                if (i > 0)
                {
                    await Task.Delay(TimeSpan.FromSeconds(i), cancellationToken);
                }

                ++i;

                var startInfo = PrepareToRunGit(folder, true);
                startInfo.ArgumentList.Add("rev-parse");

                // Try to force it being shown as a hash
                startInfo.ArgumentList.Add("--verify");
                startInfo.ArgumentList.Add("HEAD");

                var result = await ProcessRunHelpers.RunProcessAsync(startInfo, cancellationToken);

                if (result.ExitCode != 0)
                {
                    if (i < attempts)
                    {
                        continue;
                    }

                    throw new Exception(
                              $"Failed to run rev-parse in repo, process exited with error: {result.FullOutput}");
                }

                var resultText = result.Output.Trim();

                if (string.IsNullOrEmpty(resultText))
                {
                    if (i < attempts)
                    {
                        continue;
                    }

                    throw new Exception(
                              $"Failed to run rev-parse in repo, empty output (code: {result.ExitCode}). " +
                              $"Error output (if any): {result.ErrorOut}, normal output: {result.Output}");
                }

                // Looks like sometimes the result is truncated hash, try to detect that here and fail
                if (resultText.Length < 20)
                {
                    if (i < attempts)
                    {
                        continue;
                    }

                    throw new Exception(
                              $"Failed to run rev-parse in repo, output is not full hash length (code: {result.ExitCode}). " +
                              $"Error output (if any): {result.ErrorOut}, normal output: {result.Output}");
                }

                return(resultText);
            }
        }