public void UnsetAll(GitConfigurationLevel level, string name, string valueRegex)
        {
            EnsureSpecificLevel(level);

            string levelArg = GetLevelFilterArg(level);
            var    gitArgs  = $"config {levelArg} --unset-all {QuoteCmdArg(name)}";

            if (valueRegex != null)
            {
                gitArgs += $" {QuoteCmdArg(valueRegex)}";
            }

            using (Process git = _git.CreateProcess(gitArgs))
            {
                git.Start();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                case 5:     // Trying to unset a value that does not exist
                    break;

                default:
                    _trace.WriteLine($"Failed to unset all multivar '{name}' with value regex '{valueRegex}' (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, $"Failed to unset all Git configuration multi-valued entries '{name}'");
                }
            }
        }
        public IEnumerable <string> GetRegex(GitConfigurationLevel level, GitConfigurationType type, string nameRegex, string valueRegex)
        {
            string levelArg = GetLevelFilterArg(level);
            string typeArg  = GetCanonicalizeTypeArg(type);

            var gitArgs = $"config --null {levelArg} {typeArg} --get-regex {QuoteCmdArg(nameRegex)}";

            if (valueRegex != null)
            {
                gitArgs += $" {QuoteCmdArg(valueRegex)}";
            }

            using (Process git = _git.CreateProcess(gitArgs))
            {
                git.Start();
                // To avoid deadlocks, always read the output stream first and then wait
                // TODO: don't read in all the data at once; stream it
                string data = git.StandardOutput.ReadToEnd();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                case 1:     // No results
                    break;

                default:
                    _trace.WriteLine($"Failed to get all multivar regex '{nameRegex}' and value regex '{valueRegex}' (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, $"Failed to get Git configuration multi-valued entries with name regex '{nameRegex}'");
                }

                string[] entries = data.Split('\0');
                foreach (string entry in entries)
                {
                    string[] kvp = entry.Split(new[] { '\n' }, count: 2);

                    if (kvp.Length == 2)
                    {
                        yield return(kvp[1]);
                    }
                }
            }
        }
        public void Add(GitConfigurationLevel level, string name, string value)
        {
            EnsureSpecificLevel(level);

            string levelArg = GetLevelFilterArg(level);

            using (Process git = _git.CreateProcess($"config {levelArg} --add {QuoteCmdArg(name)} {QuoteCmdArg(value)}"))
            {
                git.Start();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                    break;

                default:
                    _trace.WriteLine($"Failed to add config entry '{name}' with value '{value}' (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, $"Failed to add Git configuration entry '{name}'");
                }
            }
        }
        public void Unset(GitConfigurationLevel level, string name)
        {
            EnsureSpecificLevel(level);

            string levelArg = GetLevelFilterArg(level);

            using (Process git = _git.CreateProcess($"config {levelArg} --unset {QuoteCmdArg(name)}"))
            {
                git.Start();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                case 5:     // Trying to unset a value that does not exist
                    break;

                default:
                    _trace.WriteLine($"Failed to unset config entry '{name}' (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, $"Failed to unset Git configuration entry '{name}'");
                }
            }
        }
        public IEnumerable <string> GetAll(GitConfigurationLevel level, GitConfigurationType type, string name)
        {
            string levelArg = GetLevelFilterArg(level);
            string typeArg  = GetCanonicalizeTypeArg(type);

            var gitArgs = $"config --null {levelArg} {typeArg} --get-all {QuoteCmdArg(name)}";

            using (Process git = _git.CreateProcess(gitArgs))
            {
                git.Start();

                // TODO: don't read in all the data at once; stream it
                string data = git.StandardOutput.ReadToEnd();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                    string[] entries = data.Split('\0');

                    // Because each line terminates with the \0 character, splitting leaves us with one
                    // bogus blank entry at the end of the array which we should ignore
                    for (var i = 0; i < entries.Length - 1; i++)
                    {
                        yield return(entries[i]);
                    }
                    break;

                case 1:     // No results
                    break;

                default:
                    _trace.WriteLine($"Failed to get all config entries '{name}' (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, $"Failed to get all Git configuration entries '{name}'");
                }
            }
        }
        public void Enumerate(GitConfigurationLevel level, GitConfigurationEnumerationCallback cb)
        {
            string levelArg = GetLevelFilterArg(level);

            using (Process git = _git.CreateProcess($"config --null {levelArg} --list"))
            {
                git.Start();
                // To avoid deadlocks, always read the output stream first and then wait
                // TODO: don't read in all the data at once; stream it
                string data = git.StandardOutput.ReadToEnd();
                git.WaitForExit();

                switch (git.ExitCode)
                {
                case 0:     // OK
                    break;

                default:
                    _trace.WriteLine($"Failed to enumerate config entries (exit={git.ExitCode}, level={level})");
                    throw GitProcess.CreateGitException(git, "Failed to enumerate all Git configuration entries");
                }

                var name  = new StringBuilder();
                var value = new StringBuilder();
                int i     = 0;
                while (i < data.Length)
                {
                    name.Clear();
                    value.Clear();

                    // Read key name (LF terminated)
                    while (i < data.Length && data[i] != '\n')
                    {
                        name.Append(data[i++]);
                    }

                    if (i >= data.Length)
                    {
                        _trace.WriteLine("Invalid Git configuration output. Expected newline terminator (\\n) after key.");
                        break;
                    }

                    // Skip the LF terminator
                    i++;

                    // Read value (null terminated)
                    while (i < data.Length && data[i] != '\0')
                    {
                        value.Append(data[i++]);
                    }

                    if (i >= data.Length)
                    {
                        _trace.WriteLine("Invalid Git configuration output. Expected null terminator (\\0) after value.");
                        break;
                    }

                    // Skip the null terminator
                    i++;

                    var entry = new GitConfigurationEntry(name.ToString(), value.ToString());

                    if (!cb(entry))
                    {
                        break;
                    }
                }
            }
        }