private void button1_Click(object sender, EventArgs e)
        {
            GitArgumentBuilder args = new("branch") { "-a" };

            string[] references = _gitUiCommands.GitModule.GitExecutable.GetOutput(args)
                                  .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

            if (references.Length == 0)
            {
                MessageBox.Show(this, "No remote branches found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                DialogResult = DialogResult.Cancel;
                return;
            }

            foreach (string reference in references)
            {
                try
                {
                    string branchName = reference.Trim('*', ' ', '\n', '\r');

                    if (branchName.StartsWith("remotes/" + _NO_TRANSLATE_Remote.Text + "/"))
                    {
                        args = new GitArgumentBuilder("branch")
                        {
                            "--track",
                            branchName.Replace($"remotes/{_NO_TRANSLATE_Remote.Text}/", ""),
                            branchName
                        };
                        _gitUiCommands.GitModule.GitExecutable.GetOutput(args);
                    }
                }
                catch
                {
                }
            }

            MessageBox.Show(this, string.Format("{0} local tracking branches have been created/updated.", references.Length),
                            "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
            Close();
        }
    }
Exemplo n.º 2
0
        private string CalculateRemoteBranchNameBasedOnBranchesText()
        {
            if (!Branches.Text.IsNullOrEmpty())
            {
                return(Branches.Text);
            }

            string remoteBranchName = Module.GetSetting(string.Format("branch.{0}.merge", _branch));

            if (!remoteBranchName.IsNullOrEmpty())
            {
                var args = new GitArgumentBuilder("name-rev")
                {
                    "--name-only",
                    remoteBranchName.QuoteNE()
                };
                remoteBranchName = Module.GitExecutable.GetOutput(args).Trim();
            }

            return(remoteBranchName);
        }
Exemplo n.º 3
0
        private void StageSubmodule()
        {
            using (var form = new FormStatus(ProcessStart, string.Format(_stageFilename.Text, _filename)))
            {
                form.ShowDialogOnError(this);
            }

            void ProcessStart(FormStatus form)
            {
                form.AddMessageLine(string.Format(_stageFilename.Text, _filename));
                var args = new GitArgumentBuilder("add")
                {
                    "--",
                    _filename.QuoteNE()
                };
                string output = Module.RunGitCmd(args);

                form.AddMessageLine(output);
                form.Done(isSuccess: string.IsNullOrWhiteSpace(output));
            }
        }
Exemplo n.º 4
0
        private void LoadBaseBranches()
        {
            var branchType       = cbType.SelectedValue.ToString();
            var manageBaseBranch = branchType != Branch.release.ToString("G");

            pnlBasedOn.Visible = manageBaseBranch;

            if (manageBaseBranch)
            {
                cbBaseBranch.DataSource = GetLocalBranches();
            }

            List <string> GetLocalBranches()
            {
                var args = new GitArgumentBuilder("branch");

                return(_gitUiCommands.GitModule
                       .GitExecutable.GetOutput(args)
                       .Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim('*', ' ', '\n', '\r'))
                       .ToList());
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Returns the branches of a remote repository as strings; ignores git errors and warnings.
        /// </summary>
        /// 'git ls-remotes --heads "URL"' is completely independent from a local repo clone.
        /// Hence there is no need for a GitModule.
        /// <param name="gitExecutable">the git executable.</param>
        /// <param name="url">the repo URL; can also be a local path.</param>
        private static IEnumerable <string> LoadRemoteRepoBranches(IExecutable gitExecutable, string url)
        {
            if (string.IsNullOrWhiteSpace(url))
            {
                return(Array.Empty <string>());
            }

            var gitArguments = new GitArgumentBuilder("ls-remote")
            {
                "--heads", url.ToPosixPath().Quote()
            };
            var heads = gitExecutable.GetOutput(gitArguments);

            return(heads.LazySplit('\n', StringSplitOptions.RemoveEmptyEntries)
                   .Select(head =>
            {
                int branchIndex = head.IndexOf(GitRefName.RefsHeadsPrefix);
                return branchIndex == -1 ? null : head.Substring(branchIndex + GitRefName.RefsHeadsPrefix.Length);
            })
                   .WhereNotNull()
                   .ToImmutableList());
        }
Exemplo n.º 6
0
        public async Task Validate_GetRevisionTagSignatureStatusAsync_one_tag(TagStatus tagStatus, string gitCmdReturn)
        {
            var objectId = ObjectId.Random();

            var gitRef = new GitRef(_module(), objectId, "refs/tags/FirstTag^{}");

            var revision = new GitRevision(objectId)
            {
                Refs = new[] { gitRef }
            };
            var args = new GitArgumentBuilder("verify-tag")
            {
                "--raw",
                gitRef.LocalName
            };

            _module().GitExecutable.GetOutput(args).Returns(gitCmdReturn);

            var actual = await _gpgController.GetRevisionTagSignatureStatusAsync(revision);

            Assert.AreEqual(tagStatus, actual);
        }
        /// <inheritdoc />
        public Encoding Read()
        {
            try
            {
                Encoding systemEncoding;

                // invoke a git command that returns an invalid argument in its response, and
                // check if a unicode-only character is reported back. If so assume msysgit-unicode

                // git config --get with a malformed key (no section) returns:
                // "error: key does not contain a section: <key>"
                const string controlStr = "ą"; // "a caudata"
                var          arguments  = new GitArgumentBuilder("config")
                {
                    "--get",
                    controlStr
                };

                string s = _module.GitExecutable.GetOutput(arguments, outputEncoding: Encoding.UTF8);
                if (s != null && s.IndexOf(controlStr) != -1)
                {
                    systemEncoding = new UTF8Encoding(false);
                }
                else
                {
                    systemEncoding = Encoding.Default;
                }

                Debug.WriteLine("System encoding: " + systemEncoding.EncodingName);

                return(systemEncoding);
            }
            catch (Exception)
            {
                // Ignore exception. If the git location itself is not configured correctly yet, we could never execute it.
                return(Encoding.Default);
            }
        }
Exemplo n.º 8
0
        private IEnumerable <string> GetObsoleteBranchNames(RefreshContext context, string curBranch)
        {
            RegexOptions options;

            if (context.RegexIgnoreCase)
            {
                options = RegexOptions.Compiled | RegexOptions.IgnoreCase;
            }
            else
            {
                options = RegexOptions.Compiled;
            }

            var  regex          = string.IsNullOrEmpty(context.RegexFilter) ? null : new Regex(context.RegexFilter, options);
            bool regexMustMatch = !context.RegexDoesNotMatch;

            var args = new GitArgumentBuilder("branch")
            {
                "--list",
                { context.IncludeRemotes, "-r" },
                { !context.IncludeUnmerged, "--merged" },
                context.ReferenceBranch
            };

            var result = context.Commands.GitExecutable.Execute(args);

            if (!result.ExitedSuccessfully)
            {
                MessageBox.Show(this, result.AllOutput, $"git {args}", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return(Array.Empty <string>());
            }

            return(_commandOutputParser.GetBranchNames(result.AllOutput)
                   .Where(branchName => branchName != curBranch && branchName != context.ReferenceBranch)
                   .Where(branchName => (!context.IncludeRemotes || branchName.StartsWith(context.RemoteRepositoryName + "/")) &&
                          (regex == null || regex.IsMatch(branchName) == regexMustMatch)));
        }
Exemplo n.º 9
0
        private void LoadFileHistory()
        {
            FileChanges.Visible = true;

            if (string.IsNullOrEmpty(FileName))
            {
                return;
            }

            _asyncLoader.LoadAsync(
                () => BuildFilter(),
                filter =>
            {
                FileChanges.SetFilters(filter);
                FileChanges.Load();
            });

            return;

            (string revision, string path) BuildFilter()
            {
                var fileName = FileName;

                // Replace windows path separator to Linux path separator.
                // This is needed to keep the file history working when started from file tree in
                // browse dialog.
                fileName = fileName.ToPosixPath();

                // we will need this later to look up proper casing for the file
                var fullFilePath = _fullPathResolver.Resolve(fileName);

                // The section below contains native windows (kernel32) calls
                // and breaks on Linux. Only use it on Windows. Casing is only
                // a Windows problem anyway.
                if (EnvUtils.RunningOnWindows() && File.Exists(fullFilePath))
                {
                    // grab the 8.3 file path
                    var shortPath = new StringBuilder(4096);
                    NativeMethods.GetShortPathName(fullFilePath, shortPath, shortPath.Capacity);

                    // use 8.3 file path to get properly cased full file path
                    var longPath = new StringBuilder(4096);
                    NativeMethods.GetLongPathName(shortPath.ToString(), longPath, longPath.Capacity);

                    // remove the working directory and now we have a properly cased file name.
                    fileName = longPath.ToString().Substring(Module.WorkingDir.Length).ToPosixPath();
                }

                if (fileName.StartsWith(Module.WorkingDir, StringComparison.InvariantCultureIgnoreCase))
                {
                    fileName = fileName.Substring(Module.WorkingDir.Length);
                }

                FileName = fileName;

                var res = (revision : (string)null, path : $" \"{fileName}\"");

                if (AppSettings.FollowRenamesInFileHistory && !Directory.Exists(fullFilePath))
                {
                    // git log --follow is not working as expected (see  http://kerneltrap.org/mailarchive/git/2009/1/30/4856404/thread)
                    //
                    // But we can take a more complicated path to get reasonable results:
                    //  1. use git log --follow to get all previous filenames of the file we are interested in
                    //  2. use git log "list of files names" to get the history graph
                    //
                    // note: This implementation is quite a quick hack (by someone who does not speak C# fluently).
                    //

                    var args = new GitArgumentBuilder("log")
                    {
                        "--format=\"%n\"",
                        "--name-only",
                        "--format",
                        GitCommandHelpers.FindRenamesAndCopiesOpts(),
                        "--",
                        fileName.Quote()
                    };

                    var listOfFileNames = new StringBuilder(fileName.Quote());

                    // keep a set of the file names already seen
                    var setOfFileNames = new HashSet <string> {
                        fileName
                    };

                    var lines = Module.GetGitOutputLines(args, GitModule.LosslessEncoding);

                    foreach (var line in lines.Select(GitModule.ReEncodeFileNameFromLossless))
                    {
                        if (!string.IsNullOrEmpty(line) && setOfFileNames.Add(line))
                        {
                            listOfFileNames.Append(" \"");
                            listOfFileNames.Append(line);
                            listOfFileNames.Append('\"');
                        }
                    }

                    // here we need --name-only to get the previous filenames in the revision graph
                    res.path      = listOfFileNames.ToString();
                    res.revision += " --name-only --parents" + GitCommandHelpers.FindRenamesAndCopiesOpts();
                }
                else if (AppSettings.FollowRenamesInFileHistory)
                {
                    // history of a directory
                    // --parents doesn't work with --follow enabled, but needed to graph a filtered log
                    res.revision = " " + GitCommandHelpers.FindRenamesOpt() + " --follow --parents";
                }
                else
                {
                    // rename following disabled
                    res.revision = " --parents";
                }

                if (AppSettings.FullHistoryInFileHistory)
                {
                    res.revision = string.Concat(" --full-history --simplify-merges ", res.revision);
                }

                return(res);
            }
        }
Exemplo n.º 10
0
        private void Delete_Click(object sender, EventArgs e)
        {
            var selectedBranches = _branches.Where(branch => branch.Delete).ToList();

            if (selectedBranches.Count == 0)
            {
                MessageBox.Show(string.Format(_selectBranchesToDelete.Text, _NO_TRANSLATE_deleteDataGridViewCheckBoxColumn.HeaderText), _deleteCaption.Text);
                return;
            }

            if (MessageBox.Show(this, string.Format(_areYouSureToDelete.Text, selectedBranches.Count), _deleteCaption.Text, MessageBoxButtons.YesNo) != DialogResult.Yes)
            {
                return;
            }

            var remoteName           = _NO_TRANSLATE_Remote.Text;
            var remoteBranchPrefix   = remoteName + "/";
            var remoteBranchesSource = IncludeRemoteBranches.Checked
                ? selectedBranches.Where(branch => branch.Name.StartsWith(remoteBranchPrefix))
                : Enumerable.Empty <Branch>();
            var remoteBranches = remoteBranchesSource.ToList();

            if (remoteBranches.Count > 0)
            {
                var message = string.Format(_dangerousAction.Text, remoteName);
                if (MessageBox.Show(this, message, _deleteCaption.Text, MessageBoxButtons.YesNo) != DialogResult.Yes)
                {
                    return;
                }
            }

            var localBranches = selectedBranches.Except(remoteBranches).ToList();

            tableLayoutPanel2.Enabled = tableLayoutPanel3.Enabled = false;
            imgLoading.Visible        = true;
            lblStatus.Text            = _deletingBranches.Text;

            ThreadHelper.JoinableTaskFactory.RunAsync(async() =>
            {
                await TaskScheduler.Default.SwitchTo(alwaysYield: true);
                foreach (var remoteBranch in remoteBranches)
                {
                    // Delete branches one by one, because it is possible one fails
                    var remoteBranchNameOffset = remoteBranchPrefix.Length;
                    var args = new GitArgumentBuilder("push")
                    {
                        remoteName,
                        $":{remoteBranch.Name.Substring(remoteBranchNameOffset)}"
                    };
                    _gitCommands.GitExecutable.GetOutput(args);
                }

                foreach (var localBranch in localBranches)
                {
                    var args = new GitArgumentBuilder("branch")
                    {
                        "-d",
                        localBranch.Name
                    };
                    _gitCommands.GitExecutable.GetOutput(args);

                    // Delete branches one by one, because it is possible one fails
                    _gitCommands.GitExecutable.GetOutput(args);
                }

                await this.SwitchToMainThreadAsync();

                tableLayoutPanel2.Enabled = tableLayoutPanel3.Enabled = true;
                await RefreshObsoleteBranchesAsync().ConfigureAwait(false);
            });
        }
Exemplo n.º 11
0
        private void RecreateObservable()
        {
            CancelBackgroundOperation();

            int fetchInterval = _fetchInterval.ValueOrDefault(Settings);

            var gitModule = _currentGitUiCommands.GitModule;

            if (fetchInterval > 0 && gitModule.IsValidGitWorkingDir())
            {
                _cancellationToken =
                    Observable.Timer(TimeSpan.FromSeconds(Math.Max(5, fetchInterval)))
                    .SelectMany(i =>
                {
                    // if git not running - start fetch immediately
                    if (!gitModule.IsRunningGitProcess())
                    {
                        return(Observable.Return(i));
                    }

                    // in other case - every 5 seconds check if git still running
                    return(Observable
                           .Interval(TimeSpan.FromSeconds(5))
                           .SkipWhile(ii => gitModule.IsRunningGitProcess())
                           .FirstAsync()
                           );
                })
                    .Repeat()
                    .ObserveOn(ThreadPoolScheduler.Instance)
                    .Subscribe(i =>
                {
                    GitArgumentBuilder args;
                    if (_fetchAllSubmodules.ValueOrDefault(Settings))
                    {
                        args = new GitArgumentBuilder("submodule")
                        {
                            "foreach",
                            "--recursive",
                            "git",
                            "fetch",
                            "--all"
                        };

                        _currentGitUiCommands.GitModule.GitExecutable.GetOutput(args);
                    }

                    var gitCmd = _gitCommand.ValueOrDefault(Settings).Trim().SplitBySpace();
                    args       = new GitArgumentBuilder(gitCmd[0])
                    {
                        gitCmd.Skip(1)
                    };
                    var msg = _currentGitUiCommands.GitModule.GitExecutable.GetOutput(args);
                    if (_autoRefresh.ValueOrDefault(Settings))
                    {
                        if (gitCmd[0].Equals("fetch", StringComparison.InvariantCultureIgnoreCase))
                        {
                            if (msg.Contains("From"))
                            {
                                _currentGitUiCommands.RepoChangedNotifier.Notify();
                            }
                        }
                        else
                        {
                            _currentGitUiCommands.RepoChangedNotifier.Notify();
                        }
                    }
                });
            }
        }
Exemplo n.º 12
0
        private void FindLargeFilesFunction()
        {
            try
            {
                var data = GetLargeFiles(_threshold);
                Dictionary <string, DateTime> revData = new Dictionary <string, DateTime>();
                foreach (var d in data)
                {
                    string   commit = d.Commit.First();
                    DateTime date;
                    if (!revData.ContainsKey(commit))
                    {
                        var args = new GitArgumentBuilder("show")
                        {
                            "-s",
                            commit,
                            "--format=\"%ci\""
                        };
                        string revDate = _gitCommands.GitExecutable.GetOutput(args);
                        DateTime.TryParse(revDate, out date);
                        revData.Add(commit, date);
                    }
                    else
                    {
                        date = revData[commit];
                    }

                    if (!_list.TryGetValue(d.SHA, out var curGitObject))
                    {
                        d.LastCommitDate = date;
                        _list.Add(d.SHA, d);
                        ThreadHelper.JoinableTaskFactory.Run(async() =>
                        {
                            await BranchesGrid.SwitchToMainThreadAsync();
                            _gitObjects.Add(d);
                        });
                    }
                    else if (!curGitObject.Commit.Contains(commit))
                    {
                        if (curGitObject.LastCommitDate < date)
                        {
                            curGitObject.LastCommitDate = date;
                        }

                        ThreadHelper.JoinableTaskFactory.Run(async() =>
                        {
                            await BranchesGrid.SwitchToMainThreadAsync();
                            _gitObjects.ResetItem(_gitObjects.IndexOf(curGitObject));
                        });
                        curGitObject.Commit.Add(commit);
                    }
                }

                string objectsPackDirectory = _gitCommands.ResolveGitInternalPath("objects/pack/");
                if (Directory.Exists(objectsPackDirectory))
                {
                    var packFiles = Directory.GetFiles(objectsPackDirectory, "pack-*.idx");
                    foreach (var pack in packFiles)
                    {
                        var args = new GitArgumentBuilder("verify-pack")
                        {
                            "-v",
                            pack
                        };

                        string[] objects = _gitCommands.GitExecutable.GetOutput(args).Split('\n');
                        ThreadHelper.JoinableTaskFactory.Run(async() =>
                        {
                            await pbRevisions.SwitchToMainThreadAsync();
                            pbRevisions.Value = pbRevisions.Value + (int)((_revList.Length * 0.1f) / packFiles.Length);
                        });
                        foreach (var gitObject in objects.Where(x => x.Contains(" blob ")))
                        {
                            string[] dataFields = gitObject.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                            if (_list.TryGetValue(dataFields[0], out var curGitObject))
                            {
                                if (int.TryParse(dataFields[3], out var compressedSize))
                                {
                                    curGitObject.CompressedSizeInBytes = compressedSize;
                                    ThreadHelper.JoinableTaskFactory.Run(async() =>
                                    {
                                        await BranchesGrid.SwitchToMainThreadAsync();
                                        _gitObjects.ResetItem(_gitObjects.IndexOf(curGitObject));
                                    });
                                }
                            }
                        }
                    }
                }

                ThreadHelper.JoinableTaskFactory.Run(async() =>
                {
                    await pbRevisions.SwitchToMainThreadAsync();
                    pbRevisions.Hide();
                });
                ThreadHelper.JoinableTaskFactory.Run(async() =>
                {
                    await BranchesGrid.SwitchToMainThreadAsync();
                    BranchesGrid.ReadOnly = false;
                });
            }
            catch
            {
            }
        }
Exemplo n.º 13
0
        public void Validate_GetTagVerifyMessage(int usefulTagRefNumber, string expected)
        {
            var objectId = ObjectId.Random();
            var revision = new GitRevision(objectId);

            switch (usefulTagRefNumber)
            {
            case 0:
            {
                // Tag but not dereference
                var gitRef = new GitRef(_module(), objectId, "refs/tags/TagName");
                revision.Refs = new[] { gitRef };

                var args = new GitArgumentBuilder("verify-tag")
                {
                    gitRef.LocalName
                };
                _module().GitExecutable.GetOutput(args).Returns("");

                break;
            }

            case 1:
            {
                // One tag that's also IsDereference == true
                var gitRef = new GitRef(_module(), objectId, "refs/tags/TagName^{}");
                revision.Refs = new[] { gitRef };

                var args = new GitArgumentBuilder("verify-tag")
                {
                    gitRef.LocalName
                };
                _module().GitExecutable.GetOutput(args).Returns(gitRef.LocalName);

                break;
            }

            case 2:
            {
                // Two tag that's also IsDereference == true
                var gitRef1 = new GitRef(_module(), objectId, "refs/tags/FirstTag^{}");

                var args = new GitArgumentBuilder("verify-tag")
                {
                    gitRef1.LocalName
                };
                _module().GitExecutable.GetOutput(args).Returns(gitRef1.LocalName);

                var gitRef2 = new GitRef(_module(), objectId, "refs/tags/SecondTag^{}");
                revision.Refs = new[] { gitRef1, gitRef2 };

                args = new GitArgumentBuilder("verify-tag")
                {
                    gitRef2.LocalName
                };
                _module().GitExecutable.GetOutput(args).Returns(gitRef2.LocalName);

                break;
            }
            }

            var actual = _gpgController.GetTagVerifyMessage(revision);

            Assert.AreEqual(expected, actual);
        }
Exemplo n.º 14
0
        private void LoadFileHistory()
        {
            FileChanges.Visible = true;

            if (string.IsNullOrEmpty(FileName))
            {
                return;
            }

            _asyncLoader.LoadAsync(
                () => BuildFilter(),
                filter =>
            {
                FileChanges.SetFilters(filter);
                FileChanges.Load();
            });

            return;

            (string revision, string path) BuildFilter()
            {
                var fileName = FileName;

                // Replace windows path separator to Linux path separator.
                // This is needed to keep the file history working when started from file tree in
                // browse dialog.
                FileName = fileName.ToPosixPath();

                var res          = (revision : (string)null, path : $" \"{fileName}\"");
                var fullFilePath = _fullPathResolver.Resolve(fileName);

                if (AppSettings.FollowRenamesInFileHistory && !Directory.Exists(fullFilePath))
                {
                    // git log --follow is not working as expected (see  http://kerneltrap.org/mailarchive/git/2009/1/30/4856404/thread)
                    //
                    // But we can take a more complicated path to get reasonable results:
                    //  1. use git log --follow to get all previous filenames of the file we are interested in
                    //  2. use git log "list of files names" to get the history graph
                    //
                    // note: This implementation is quite a quick hack (by someone who does not speak C# fluently).
                    //

                    var args = new GitArgumentBuilder("log")
                    {
                        "--format=\"%n\"",
                        "--name-only",
                        "--follow",
                        GitCommandHelpers.FindRenamesAndCopiesOpts(),
                        "--",
                        fileName.Quote()
                    };

                    var listOfFileNames = new StringBuilder(fileName.Quote());

                    // keep a set of the file names already seen
                    var setOfFileNames = new HashSet <string> {
                        fileName
                    };

                    var lines = Module.GitExecutable.GetOutputLines(args, outputEncoding: GitModule.LosslessEncoding);

                    foreach (var line in lines.Select(GitModule.ReEncodeFileNameFromLossless))
                    {
                        if (!string.IsNullOrEmpty(line) && setOfFileNames.Add(line))
                        {
                            listOfFileNames.Append(" \"");
                            listOfFileNames.Append(line);
                            listOfFileNames.Append('\"');
                        }
                    }

                    // here we need --name-only to get the previous filenames in the revision graph
                    res.path      = listOfFileNames.ToString();
                    res.revision += " --name-only --parents" + GitCommandHelpers.FindRenamesAndCopiesOpts();
                }
                else if (AppSettings.FollowRenamesInFileHistory)
                {
                    // history of a directory
                    // --parents doesn't work with --follow enabled, but needed to graph a filtered log
                    res.revision = " " + GitCommandHelpers.FindRenamesOpt() + " --follow --parents";
                }
                else
                {
                    // rename following disabled
                    res.revision = " --parents";
                }

                if (AppSettings.FullHistoryInFileHistory)
                {
                    res.revision = string.Concat(" --full-history ", AppSettings.SimplifyMergesInFileHistory ? "--simplify-merges " : string.Empty, res.revision);
                }

                return(res);
            }
        }
        // This method is required to facilitate unit tests
        private IDictionary <string, AheadBehindData> GetData(Encoding encoding, string branchName = "")
        {
            if (branchName == null)
            {
                throw new ArgumentException(nameof(branchName));
            }

            if (branchName == DetachedHeadParser.DetachedBranch)
            {
                return(null);
            }

            var aheadBehindGitCommand = new GitArgumentBuilder("for-each-ref")
            {
                $"--color=never",
                $"--format=\"{_refFormat}\"",
                "refs/heads/" + branchName
            };

            var result = GetGitExecutable().GetOutput(aheadBehindGitCommand, outputEncoding: encoding);

            if (string.IsNullOrEmpty(result))
            {
                return(null);
            }

            var matches = _aheadBehindRegEx.Matches(result);
            var aheadBehindForBranchesData = new Dictionary <string, AheadBehindData>();

            foreach (Match match in matches)
            {
                var branch    = match.Groups["branch"].Value;
                var remoteRef = (match.Groups["remote_p"].Success && !string.IsNullOrEmpty(match.Groups["remote_p"].Value))
                    ? match.Groups["remote_p"].Value
                    : match.Groups["remote_u"].Value;
                if (string.IsNullOrEmpty(branch) || string.IsNullOrEmpty(remoteRef))
                {
                    continue;
                }

#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
                aheadBehindForBranchesData.Add(match.Groups["branch"].Value,
                                               new AheadBehindData
                {
                    // The information is displayed in the push button, so the push info is preferred (may differ from upstream)
                    Branch     = branch,
                    RemoteRef  = remoteRef,
                    AheadCount =
                        // Prefer push to upstream for the count
                        match.Groups["ahead_p"].Success
                        // Single-line comment should be preceded by blank line
                            ? match.Groups["ahead_p"].Value
                        // If behind is set for push, ahead is null
                            : match.Groups["behind_p"].Success
                            ? string.Empty
                            : match.Groups["ahead_u"].Success
                            ? match.Groups["ahead_u"].Value
                        // No information about the remote branch, it is gone
                            : match.Groups["gone_p"].Success || match.Groups["gone_u"].Success
                            ? AheadBehindData.Gone
                        // If the printout is unknown (translated?), do not assume that there are "0" changes
                            : (match.Groups["unk_p"].Success && !string.IsNullOrWhiteSpace(match.Groups["unk_p"].Value)) ||
                        (match.Groups["unk_u"].Success && !string.IsNullOrWhiteSpace(match.Groups["unk_u"].Value))
                            ? string.Empty
                        // A remote exists, but "track" does not display the count if ahead/behind match
                            : "0",

                    // Behind do not track '0' or 'gone', only in Ahead
                    BehindCount = match.Groups["behind_p"].Success
                            ? match.Groups["behind_p"].Value
                            : !match.Groups["ahead_p"].Success
                            ? match.Groups["behind_u"].Value
                            : string.Empty
                });
#pragma warning restore SA1515
            }

            return(aheadBehindForBranchesData);
        }