Files
Explorer-Workbench/tests/Explorer.Application.Tests/GitPorcelainParserTests.cs

223 lines
8.1 KiB
C#

using Explorer.Application;
using Explorer.Domain;
namespace Explorer.Application.Tests;
public class GitPorcelainParserTests
{
[Fact]
public void Parses_branch_counts_and_ahead_behind()
{
var status = GitPorcelainParser.Parse("""
# branch.oid abcdef1234567890
# branch.head main
# branch.upstream origin/main
# branch.ab +2 -1
1 .M N... 100644 100644 100644 a a src/App.cs
1 M. N... 100644 100644 100644 b b README.md
2 R. N... 100644 100644 100644 c c R100 new.txt old.txt
? bin/out.dll
? notes.md
! ignore.me
""", @"C:\src");
Assert.NotNull(status);
Assert.Equal("main", status.Branch);
Assert.Equal(3, status.ModifiedCount);
Assert.Equal(2, status.UntrackedCount);
Assert.Equal(2, status.Ahead);
Assert.Equal(1, status.Behind);
Assert.False(status.WorkingTreeClean);
Assert.Equal("main · 3 modified · 2 untracked · 2 ahead · 1 behind", status.Badge);
Assert.Collection(
status.Changes,
c =>
{
Assert.Equal(GitChangeState.Staged, c.State);
Assert.Equal("old.txt", c.OriginalPath);
Assert.Equal("new.txt", c.Path);
Assert.Equal("old.txt → new.txt", c.DisplayPath);
Assert.Equal("renamed", c.ChangeLabel);
},
c =>
{
Assert.Equal(GitChangeState.Staged, c.State);
Assert.Equal("README.md", c.Path);
Assert.Equal("modified", c.ChangeLabel);
},
c =>
{
Assert.Equal(GitChangeState.Unstaged, c.State);
Assert.Equal("src/App.cs", c.Path);
Assert.Equal("modified", c.ChangeLabel);
},
c =>
{
Assert.Equal(GitChangeState.Untracked, c.State);
Assert.Equal("bin/out.dll", c.Path);
},
c =>
{
Assert.Equal(GitChangeState.Untracked, c.State);
Assert.Equal("notes.md", c.Path);
});
}
[Fact]
public void Lists_staged_and_unstaged_for_the_same_path()
{
var status = GitPorcelainParser.Parse("""
# branch.head main
1 MM N... 100644 100644 100644 a a src/Both.cs
""", @"C:\src");
Assert.NotNull(status);
Assert.Equal(1, status.ModifiedCount);
Assert.Equal(2, status.Changes.Count);
Assert.Equal(GitChangeState.Staged, status.Changes[0].State);
Assert.Equal(GitChangeState.Unstaged, status.Changes[1].State);
Assert.All(status.Changes, c => Assert.Equal("src/Both.cs", c.Path));
}
[Fact]
public void Lists_unmerged_and_quoted_paths()
{
var status = GitPorcelainParser.Parse("""
# branch.head topic
u UU N... 100644 100644 100644 100644 a b c conflict.txt
1 .M N... 100644 100644 100644 a a "my file.txt"
? docs/a file.md
""", @"C:\src");
Assert.NotNull(status);
Assert.Equal(2, status.ModifiedCount);
Assert.Equal(1, status.UntrackedCount);
Assert.Equal(GitChangeState.Unmerged, status.Changes[0].State);
Assert.Equal("conflict.txt", status.Changes[0].Path);
Assert.Equal("unmerged", status.Changes[0].ChangeLabel);
Assert.Equal("my file.txt", status.Changes[1].Path);
Assert.Equal("docs/a file.md", status.Changes[2].Path);
}
[Fact]
public void Unquote_decodes_c_escapes()
=> Assert.Equal("a\"b", GitPorcelainParser.Unquote("\"a\\\"b\""));
[Fact]
public void Clean_tree_uses_detached_oid_prefix()
{
var status = GitPorcelainParser.Parse("""
# branch.oid 0123456789abcdef
# branch.head (detached)
""", @"F:\repo");
Assert.NotNull(status);
Assert.Equal("0123456", status.Branch);
Assert.True(status.WorkingTreeClean);
Assert.Equal("0123456 · clean", status.Badge);
}
[Fact]
public void Empty_output_is_null()
=> Assert.Null(GitPorcelainParser.Parse("", @"C:\src"));
}
public class GitLocatorTests
{
[Fact]
public void Prefers_the_configured_path_when_it_exists()
{
var path = @"C:\Tools\git.exe";
Assert.Equal(path, GitLocator.Find(path, fileExists: p => p == path, pathVariable: ""));
}
[Fact]
public void Finds_git_on_PATH()
{
var found = GitLocator.Find(
null,
fileExists: p => p.Equals(@"D:\bin\git.exe", StringComparison.OrdinalIgnoreCase),
pathVariable: @"C:\Windows;D:\bin");
Assert.Equal(@"D:\bin\git.exe", found);
}
[Fact]
public void Returns_null_when_git_is_missing()
=> Assert.Null(GitLocator.Find(null, fileExists: _ => false, pathVariable: @"C:\none"));
}
public class GitRepoDetectorTests
{
[Fact]
public void Finds_the_repo_root_from_a_nested_path()
{
var dirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\src", @"C:\src\.git", @"C:\src\app" };
var files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Assert.Equal(@"C:\src", GitRepoDetector.FindRoot(@"C:\src\app\Main.cs", dirs.Contains, files.Contains));
Assert.True(GitRepoDetector.IsRepoRoot(@"C:\src", dirs.Contains, files.Contains));
Assert.False(GitRepoDetector.IsRepoRoot(@"C:\src\app", dirs.Contains, files.Contains));
}
[Fact]
public void Treats_a_gitfile_as_a_worktree_root()
{
var dirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\work" };
var files = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\work\.git" };
Assert.Equal(@"C:\work", GitRepoDetector.FindRoot(@"C:\work", dirs.Contains, files.Contains));
}
[Fact]
public void Virtual_roots_are_not_repos()
=> Assert.Null(GitRepoDetector.FindRoot(LocationRoots.ThisPc, _ => true, _ => true));
[Fact]
public void Finds_gitdir_from_a_directory_and_a_gitfile()
{
var dirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@"C:\src",
@"C:\src\.git",
@"C:\work",
@"D:\repo\.git\worktrees\topic"
};
var files = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\work\.git" };
Assert.Equal(@"C:\src\.git", GitRepoDetector.FindGitDir(@"C:\src", dirs.Contains, files.Contains));
Assert.Equal(
@"D:\repo\.git\worktrees\topic",
GitRepoDetector.FindGitDir(
@"C:\work",
dirs.Contains,
files.Contains,
_ => "gitdir: D:/repo/.git/worktrees/topic"));
}
[Fact]
public void Detects_merge_and_rebase_in_progress()
{
var dirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@"C:\src",
@"C:\src\.git",
@"C:\src\.git\rebase-merge",
@"C:\work",
@"D:\repo\.git\worktrees\topic"
};
var files = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@"C:\src\.git\MERGE_HEAD",
@"C:\work\.git",
@"D:\repo\.git\worktrees\topic\CHERRY_PICK_HEAD"
};
Assert.Equal(GitOperationKind.Merge, GitRepoDetector.GetOperation(@"C:\src", dirs.Contains, files.Contains));
Assert.True(GitRepoDetector.IsOperationInProgress(@"C:\src", dirs.Contains, files.Contains));
dirs.Remove(@"C:\src\.git\rebase-merge");
files.Remove(@"C:\src\.git\MERGE_HEAD");
Assert.Equal(GitOperationKind.None, GitRepoDetector.GetOperation(@"C:\src", dirs.Contains, files.Contains));
Assert.False(GitRepoDetector.IsOperationInProgress(@"C:\src", dirs.Contains, files.Contains));
Assert.Equal(
GitOperationKind.CherryPick,
GitRepoDetector.GetOperation(
@"C:\work",
dirs.Contains,
files.Contains,
_ => "gitdir: D:/repo/.git/worktrees/topic"));
}
}