using Explorer.Domain; namespace Explorer.Application; public static class GitListingOverlay { public static string ForNestedRepo(GitStatus status) { var parts = new List { status.Branch }; if (!string.IsNullOrEmpty(status.OperationLabel)) { parts.Add(status.OperationLabel); } if (status.ModifiedCount > 0) { parts.Add($"{status.ModifiedCount} modified"); } if (status.UntrackedCount > 0) { parts.Add($"{status.UntrackedCount} untracked"); } return string.Join(" · ", parts); } public static string ForItem(string itemFullPath, bool isDirectory, string repoRoot, GitStatus status) { var rel = ToGitRelative(repoRoot, itemFullPath); if (rel is null) { return ""; } return isDirectory ? FolderLabel(status.Changes, rel) : FileLabel(status.Changes, rel); } internal static string? ToGitRelative(string repoRoot, string fullPath) { var root = PathRules.FromExtended(repoRoot).TrimEnd('\\'); var full = PathRules.FromExtended(fullPath).TrimEnd('\\'); if (full.Equals(root, StringComparison.OrdinalIgnoreCase)) { return ""; } if (!full.StartsWith(root + "\\", StringComparison.OrdinalIgnoreCase)) { return null; } return full[(root.Length + 1)..].Replace('\\', '/'); } private static string FileLabel(IReadOnlyList changes, string rel) { if (rel.Length == 0) { return ""; } var hits = changes.Where(c => Matches(c, rel)).ToList(); if (hits.Count == 0) { return ""; } if (hits.Exists(c => c.State == GitChangeState.Unmerged)) { return "Unmerged"; } if (hits.Exists(c => c.State == GitChangeState.Untracked)) { return "Untracked"; } var staged = hits.Find(c => c.State == GitChangeState.Staged); var unstaged = hits.Find(c => c.State == GitChangeState.Unstaged); if (staged is not null && unstaged is not null) { return "Staged · Modified"; } if (staged is not null) { return staged.Index switch { 'R' => "Renamed", 'A' => "Added", 'D' => "Deleted", _ => "Staged" }; } return unstaged is null ? "" : Title(unstaged.ChangeLabel); } private static string FolderLabel(IReadOnlyList changes, string rel) { var hits = changes.Where(c => Under(c, rel)).ToList(); if (hits.Count == 0) { return ""; } if (hits.Exists(c => c.State == GitChangeState.Unmerged)) { return "Unmerged"; } if (hits.TrueForAll(c => c.State == GitChangeState.Untracked)) { return "Untracked"; } return "Modified"; } private static bool Matches(GitChange change, string rel) => Same(change.Path, rel) || (change.OriginalPath is { Length: > 0 } original && Same(original, rel)); private static bool Under(GitChange change, string rel) { if (Matches(change, rel)) { return true; } if (rel.Length == 0) { return true; } var prefix = rel + "/"; return change.Path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) || (change.OriginalPath is { Length: > 0 } original && original.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); } private static bool Same(string left, string right) => left.Equals(right, StringComparison.OrdinalIgnoreCase); private static string Title(string value) => string.IsNullOrEmpty(value) ? "" : char.ToUpperInvariant(value[0]) + value[1..]; }