149 lines
4.0 KiB
C#
149 lines
4.0 KiB
C#
using Explorer.Domain;
|
|
|
|
namespace Explorer.Application;
|
|
|
|
public static class GitListingOverlay
|
|
{
|
|
public static string ForNestedRepo(GitStatus status)
|
|
{
|
|
var parts = new List<string> { 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<GitChange> 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<GitChange> 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..];
|
|
}
|