using Explorer.Application; using Explorer.Domain; using Explorer.Domain.Abstractions; using Explorer.Windows; namespace Explorer.Application.Tests; public class GitCommandIntegrationTests : IDisposable { private readonly string _root; private readonly WindowsGitStatusProvider _git; public GitCommandIntegrationTests() { _root = Path.Combine(Path.GetTempPath(), "ew-git", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_root); _git = new WindowsGitStatusProvider(new UiPreferencesStore(new GitTestEnv(_root))); } public void Dispose() { try { Directory.Delete(_root, true); } catch { /* ignore */ } } [Fact] public async Task Diff_stage_commit_and_merge_resolution() { if (GitLocator.Find(null) is null) { return; } var repo = Path.Combine(_root, "repo"); Directory.CreateDirectory(repo); await Git(repo, "init", "-b", "main").ConfigureAwait(true); await Git(repo, "config", "user.email", "test@example.com").ConfigureAwait(true); await Git(repo, "config", "user.name", "Test").ConfigureAwait(true); await Git(repo, "config", "core.autocrlf", "false").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "note.txt"), "base\n"); await Git(repo, "add", "note.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "base").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "note.txt"), "base\nedited\n"); var status = await _git.StatusAsync(repo).ConfigureAwait(true); Assert.NotNull(status); var unstaged = Assert.Single(status.Changes, c => c.State == GitChangeState.Unstaged); var diff = await _git.DiffAsync(repo, unstaged).ConfigureAwait(true); Assert.Null(diff.Error); Assert.Contains(diff.Lines, l => l.Kind == GitDiffLineKind.Added && l.Text.Contains("edited")); var staged = await _git.StageAsync(repo, "note.txt").ConfigureAwait(true); Assert.True(staged.Succeeded, staged.DisplayMessage); var committed = await _git.CommitAsync(repo, "edit", ["note.txt"]).ConfigureAwait(true); Assert.True(committed.Succeeded, committed.DisplayMessage); await Git(repo, "checkout", "-b", "topic").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "note.txt"), "base\nedited\ntopic\n"); await Git(repo, "add", "note.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "topic").ConfigureAwait(true); await Git(repo, "checkout", "main").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "note.txt"), "base\nedited\nmain\n"); await Git(repo, "add", "note.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "main").ConfigureAwait(true); var merge = await GitExpectFail(repo, "merge", "topic").ConfigureAwait(true); Assert.False(string.IsNullOrWhiteSpace(merge)); status = await _git.StatusAsync(repo).ConfigureAwait(true); Assert.NotNull(status); Assert.Equal(GitOperationKind.Merge, status.Operation); Assert.Contains(status.Changes, c => c.State == GitChangeState.Unmerged); var ours = await _git.CheckoutConflictAsync(repo, "note.txt", GitConflictSide.Ours).ConfigureAwait(true); Assert.True(ours.Succeeded, ours.DisplayMessage); status = await _git.StatusAsync(repo).ConfigureAwait(true); Assert.NotNull(status); Assert.False(status.HasUnmerged); var continued = await _git.ContinueOperationAsync(repo).ConfigureAwait(true); Assert.True(continued.Succeeded, continued.DisplayMessage); status = await _git.StatusAsync(repo).ConfigureAwait(true); Assert.NotNull(status); Assert.Equal(GitOperationKind.None, status.Operation); Assert.True(status.WorkingTreeClean); } [Fact] public async Task Abort_restores_the_pre_merge_tree() { if (GitLocator.Find(null) is null) { return; } var repo = Path.Combine(_root, "abort"); Directory.CreateDirectory(repo); await Git(repo, "init", "-b", "main").ConfigureAwait(true); await Git(repo, "config", "user.email", "test@example.com").ConfigureAwait(true); await Git(repo, "config", "user.name", "Test").ConfigureAwait(true); await Git(repo, "config", "core.autocrlf", "false").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "a.txt"), "one\n"); await Git(repo, "add", "a.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "one").ConfigureAwait(true); await Git(repo, "checkout", "-b", "other").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "a.txt"), "two\n"); await Git(repo, "add", "a.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "two").ConfigureAwait(true); await Git(repo, "checkout", "main").ConfigureAwait(true); File.WriteAllText(Path.Combine(repo, "a.txt"), "three\n"); await Git(repo, "add", "a.txt").ConfigureAwait(true); await Git(repo, "commit", "-m", "three").ConfigureAwait(true); await GitExpectFail(repo, "merge", "other").ConfigureAwait(true); var aborted = await _git.AbortOperationAsync(repo).ConfigureAwait(true); Assert.True(aborted.Succeeded, aborted.DisplayMessage); var status = await _git.StatusAsync(repo).ConfigureAwait(true); Assert.NotNull(status); Assert.Equal(GitOperationKind.None, status.Operation); Assert.Equal("three\n", File.ReadAllText(Path.Combine(repo, "a.txt")).Replace("\r\n", "\n")); } private static async Task Git(string repo, params string[] args) { var result = await RunGit(repo, args).ConfigureAwait(true); Assert.True(result.Exit == 0, result.Text); } private static async Task GitExpectFail(string repo, params string[] args) { var result = await RunGit(repo, args).ConfigureAwait(true); Assert.NotEqual(0, result.Exit); return result.Text; } private static async Task<(int Exit, string Text)> RunGit(string repo, string[] args) { var git = GitLocator.Find(null) ?? throw new InvalidOperationException("git.exe missing"); using var process = new System.Diagnostics.Process(); process.StartInfo.FileName = git; process.StartInfo.WorkingDirectory = repo; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; foreach (var arg in args) { process.StartInfo.ArgumentList.Add(arg); } process.Start(); var stdout = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(true); var stderr = await process.StandardError.ReadToEndAsync().ConfigureAwait(true); await process.WaitForExitAsync().ConfigureAwait(true); return (process.ExitCode, stdout + stderr); } } file sealed class GitTestEnv : IAppEnvironment { public GitTestEnv(string dir) { DataDirectory = Path.Combine(dir, "data"); Directory.CreateDirectory(DataDirectory); DatabasePath = Path.Combine(DataDirectory, "index.db"); LogDirectory = Path.Combine(DataDirectory, "logs"); Directory.CreateDirectory(LogDirectory); } public string DataDirectory { get; } public string DatabasePath { get; } public string LogDirectory { get; } }