Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ charset = utf-8
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = crlf
# LF on every platform, matching the `* text=auto eol=lf` default in .gitattributes.
# Overrides below must stay in step with the eol pins in that file.
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

Expand Down Expand Up @@ -503,4 +505,9 @@ indent_style = tab
# Shell scripts
[*.sh]
end_of_line = lf
indent_size = 2
indent_size = 2

# Windows batch scripts and Visual Studio solution files keep CRLF on every platform.
# These match the eol=crlf pins in .gitattributes.
[*.{cmd,bat,sln}]
end_of_line = crlf
23 changes: 14 additions & 9 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
# Git Line Endings #
###############################

# Set default behaviour to automatically normalize line endings.
* text=auto
# Normalize all text files to LF in the repository, and check them out as LF on every
# platform. The explicit eol overrides each machine's core.autocrlf, so the working tree
# is byte-identical on Windows, Linux and macOS. This must stay in step with the
# end_of_line settings in .editorconfig.
* text=auto eol=lf

# csharp files to match .editorconfig
*.cs text eol=crlf
# Force bash scripts to always use LF line endings so that if a repo is accessed
# in Unix via a file share from Windows, the scripts will work. Redundant with the
# default above, kept explicit because these files break outright with CRLF.
*.sh text eol=lf

# Force batch scripts to always use CRLF line endings so that if a repo is accessed
# in Windows via a file share from Linux, the scripts will work.
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.cmd text eol=crlf
*.bat text eol=crlf

# Force bash scripts to always use LF line endings so that if a repo is accessed
# in Unix via a file share from Windows, the scripts will work.
*.sh text eol=lf
# Visual Studio rewrites solution files with CRLF regardless of the checkout, so pin
# them to avoid a spurious whole-file diff every time the solution is opened.
*.sln text eol=crlf

###############################
# Git Large File System (LFS) #
Expand Down
21 changes: 1 addition & 20 deletions .runsettings
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<!-- Specify a deterministic output directory -->
<RunConfiguration>
<ResultsDirectory>.\coverage</ResultsDirectory>
<ResultsDirectory>TestResults</ResultsDirectory>
</RunConfiguration>
<!-- Specify a deterministic output directory -->
<RunConfiguration>
<ResultsDirectory>.\coverage</ResultsDirectory>
</RunConfiguration>

<!-- Configuration for coverlet data collector -->
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="coverlet.collector.DataCollectors.CoverletInProcDataCollector, coverlet.collector, Version=6.0.4.0, Culture=neutral, PublicKeyToken=null">
<Configuration>
<Format>opencover</Format>
<OutputPath>coverage.opencover.xml</OutputPath>
<Exclude>[*Test*]*,[*Tests*]*</Exclude>
<ExcludeByFile>**/obj/**/*</ExcludeByFile>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
5 changes: 2 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.10" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.10" />
<!-- Git and GitHub -->
<PackageVersion Include="LibGit2Sharp" Version="0.32.0" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<!-- Diff and Merge -->
<PackageVersion Include="DiffPlex" Version="1.9.0" />
Expand All @@ -27,7 +26,7 @@
<PackageVersion Include="ktsu.IntervalAction" Version="1.3.10" />
<PackageVersion Include="ktsu.AppDataStorage" Version="1.16.21" />
<PackageVersion Include="ktsu.Extensions" Version="1.5.14" />
<PackageVersion Include="ktsu.RunCommand" Version="1.4.5" />
<PackageVersion Include="ktsu.RunCommand" Version="1.4.26" />
<PackageVersion Include="ktsu.Frontmatter" Version="1.2.10" />
<PackageVersion Include="ktsu.TextFilter" Version="1.5.9" />
<PackageVersion Include="ktsu.Semantics.Paths" Version="1.1.0" />
Expand All @@ -36,7 +35,7 @@
<!-- SDK-required packages -->
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.301" />
<PackageVersion Include="Microsoft.SourceLink.AzureRepos.Git" Version="10.0.301" />
<PackageVersion Include="Polyfill" Version="10.8.1" />
<PackageVersion Include="Polyfill" Version="11.0.2" />
<!-- Testing -->
<!-- MSTest.TestAdapter, MSTest.TestFramework and Microsoft.Testing.Extensions.CodeCoverage
are supplied by MSTest.Sdk (pinned in global.json). Declaring them here as well makes
Expand Down
1 change: 0 additions & 1 deletion KtsuTools.Core/KtsuTools.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Spectre.Console.Ansi" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="Octokit" />
<PackageReference Include="ktsu.AppDataStorage" />
<PackageReference Include="ktsu.CredentialCache" />
Expand Down
152 changes: 60 additions & 92 deletions KtsuTools.Core/Services/Git/GitService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,114 +6,82 @@ namespace KtsuTools.Core.Services.Git;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LibGit2Sharp;

public class GitService : IGitService
{
public Task<bool> PullAsync(string repoPath, CancellationToken ct = default) =>
Task.Run(() =>
{
try
{
using Repository repo = new(repoPath);
Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now);
PullOptions options = new()
{
FetchOptions = new FetchOptions(),
};
Commands.Pull(repo, signature, options);
return true;
}
catch (LibGit2SharpException)
{
return false;
}
}, ct);
public async Task<bool> PullAsync(string repoPath, CancellationToken ct = default)
{
GitResult result = await GitCli.RunInAsync(repoPath, ct, "pull").ConfigureAwait(false);
return result.Succeeded;
}

public Task<bool> CommitAsync(string repoPath, string message, CancellationToken ct = default) =>
Task.Run(() =>
public async Task<bool> CommitAsync(string repoPath, string message, CancellationToken ct = default)
{
GitResult staged = await GitCli.RunInAsync(repoPath, ct, "add", "--all").ConfigureAwait(false);
if (!staged.Succeeded)
{
try
{
using Repository repo = new(repoPath);
Commands.Stage(repo, "*");
Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now);
repo.Commit(message, signature, signature);
return true;
}
catch (LibGit2SharpException)
{
return false;
}
}, ct);
return false;
}

public Task<bool> PushAsync(string repoPath, CancellationToken ct = default) =>
Task.Run(() =>
{
try
{
using Repository repo = new(repoPath);
Remote? remote = repo.Network.Remotes["origin"];
if (remote is null)
{
return false;
}
// git exits non-zero when there is nothing staged, which matches the previous behaviour of
// reporting failure for an empty commit.
GitResult committed = await GitCli.RunInAsync(repoPath, ct, "commit", "-m", message).ConfigureAwait(false);
return committed.Succeeded;
}

Branch branch = repo.Head;
repo.Network.Push(remote, branch.CanonicalName, new PushOptions());
return true;
}
catch (LibGit2SharpException)
{
return false;
}
}, ct);
public async Task<bool> PushAsync(string repoPath, CancellationToken ct = default)
{
// HEAD rather than a branch name so a detached head fails loudly instead of pushing the
// wrong ref, and origin explicitly because that is the remote the previous version used.
GitResult result = await GitCli.RunInAsync(repoPath, ct, "push", "origin", "HEAD").ConfigureAwait(false);
return result.Succeeded;
}

public Task<IReadOnlyList<string>> GetStatusAsync(string repoPath, CancellationToken ct = default) =>
Task.Run<IReadOnlyList<string>>(() =>
public async Task<IReadOnlyList<string>> GetStatusAsync(string repoPath, CancellationToken ct = default)
{
GitResult result = await GitCli.RunInAsync(repoPath, ct, "status", "--porcelain").ConfigureAwait(false);
if (!result.Succeeded)
{
try
{
using Repository repo = new(repoPath);
return [.. repo.RetrieveStatus().Select(e => $"{e.State}: {e.FilePath}")];
}
catch (LibGit2SharpException)
{
return [];
}
}, ct);
return [];
}

public Task<string> GetCurrentBranchAsync(string repoPath, CancellationToken ct = default) =>
Task.Run(() =>
List<string> entries = [];
foreach (string line in result.OutputLines)
{
try
// Porcelain v1 is a two character status code, a space, then the path.
if (line.Length > 3)
{
using Repository repo = new(repoPath);
return repo.Head.FriendlyName;
entries.Add($"{line[..2].Trim()}: {line[3..]}");
}
catch (LibGit2SharpException)
{
return string.Empty;
}
}, ct);
}

return entries;
}

public Task<bool> CloneAsync(Uri url, string targetPath, CancellationToken ct = default) =>
Task.Run(() =>
public async Task<string> GetCurrentBranchAsync(string repoPath, CancellationToken ct = default)
{
GitResult result = await GitCli.RunInAsync(repoPath, ct, "rev-parse", "--abbrev-ref", "HEAD").ConfigureAwait(false);
return result.Succeeded ? result.OutputText : string.Empty;
}

public async Task<bool> CloneAsync(Uri url, string targetPath, CancellationToken ct = default)
{
Ensure.NotNull(url);

GitResult result = await GitCli.RunAsync(["clone", url.AbsoluteUri, targetPath], ct).ConfigureAwait(false);
return result.Succeeded;
}

public async Task<bool> IsRepositoryAsync(string path, CancellationToken ct = default)
{
if (!Directory.Exists(path))
{
try
{
Repository.Clone(url.AbsoluteUri, targetPath);
return true;
}
catch (LibGit2SharpException)
{
return false;
}
}, ct);
return false;
}

public Task<bool> IsRepositoryAsync(string path, CancellationToken ct = default) =>
Task.Run(() => Repository.IsValid(path), ct);
GitResult result = await GitCli.RunInAsync(path, ct, "rev-parse", "--is-inside-work-tree").ConfigureAwait(false);
return result.Succeeded && result.OutputText.Equals("true", StringComparison.OrdinalIgnoreCase);
}
}
1 change: 0 additions & 1 deletion KtsuTools.Sync/KtsuTools.Sync.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<PackageReference Include="Spectre.Console" />
<PackageReference Include="Spectre.Console.Ansi" />
<PackageReference Include="ktsu.Extensions" />
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="ktsu.Semantics.Paths" />
<PackageReference Include="ktsu.Semantics.Strings" />
</ItemGroup>
Expand Down
Loading
Loading