|
@@ -1,96 +1,118 @@
|
|
|
using LibGit2Sharp;
|
|
using LibGit2Sharp;
|
|
|
|
|
+using LibGit2Sharp.Handlers;
|
|
|
|
|
|
|
|
namespace RackPeek.Domain.Git;
|
|
namespace RackPeek.Domain.Git;
|
|
|
|
|
|
|
|
-public sealed class LibGit2GitRepository : IGitRepository
|
|
|
|
|
-{
|
|
|
|
|
- private readonly string _repoPath;
|
|
|
|
|
- private bool _isAvailable;
|
|
|
|
|
-
|
|
|
|
|
- public LibGit2GitRepository(string configDirectory)
|
|
|
|
|
- {
|
|
|
|
|
- _repoPath = configDirectory;
|
|
|
|
|
- _isAvailable = Repository.IsValid(configDirectory);
|
|
|
|
|
|
|
+public interface IGitCredentialsProvider {
|
|
|
|
|
+ CredentialsHandler GetHandler();
|
|
|
|
|
+}
|
|
|
|
|
+public sealed class GitHubTokenCredentialsProvider(string username, string token) : IGitCredentialsProvider {
|
|
|
|
|
+ private readonly string _username = username ?? throw new ArgumentNullException(nameof(username));
|
|
|
|
|
+ private readonly string _token = token ?? throw new ArgumentNullException(nameof(token));
|
|
|
|
|
+
|
|
|
|
|
+ public CredentialsHandler GetHandler() {
|
|
|
|
|
+ return (_, _, _) => new UsernamePasswordCredentials {
|
|
|
|
|
+ Username = _username,
|
|
|
|
|
+ Password = _token
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+public sealed class LibGit2GitRepository(
|
|
|
|
|
+ string configDirectory,
|
|
|
|
|
+ IGitCredentialsProvider credentialsProvider) : IGitRepository {
|
|
|
|
|
+ private readonly CredentialsHandler _credentials = credentialsProvider.GetHandler();
|
|
|
|
|
+
|
|
|
|
|
+ private bool _isAvailable = Repository.IsValid(configDirectory);
|
|
|
|
|
|
|
|
public bool IsAvailable => _isAvailable;
|
|
public bool IsAvailable => _isAvailable;
|
|
|
|
|
|
|
|
- public void Init()
|
|
|
|
|
- {
|
|
|
|
|
- Repository.Init(_repoPath);
|
|
|
|
|
|
|
+ public void Init() {
|
|
|
|
|
+ Repository.Init(configDirectory);
|
|
|
_isAvailable = true;
|
|
_isAvailable = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public GitRepoStatus GetStatus()
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ private Repository OpenRepo() => new(configDirectory);
|
|
|
|
|
+
|
|
|
|
|
+ private static Signature GetSignature(Repository repo) {
|
|
|
|
|
+ var name = repo.Config.Get<string>("user.name")?.Value ?? "RackPeek";
|
|
|
|
|
+ var email = repo.Config.Get<string>("user.email")?.Value ?? "rackpeek@local";
|
|
|
|
|
+
|
|
|
|
|
+ return new Signature(name, email, DateTimeOffset.Now);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Remote GetRemote(Repository repo) => repo.Network.Remotes["origin"] ?? repo.Network.Remotes.First();
|
|
|
|
|
+
|
|
|
|
|
+ public GitRepoStatus GetStatus() {
|
|
|
if (!_isAvailable)
|
|
if (!_isAvailable)
|
|
|
return GitRepoStatus.NotAvailable;
|
|
return GitRepoStatus.NotAvailable;
|
|
|
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
- return repo.RetrieveStatus().IsDirty ? GitRepoStatus.Dirty : GitRepoStatus.Clean;
|
|
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
|
|
+ return repo.RetrieveStatus().IsDirty
|
|
|
|
|
+ ? GitRepoStatus.Dirty
|
|
|
|
|
+ : GitRepoStatus.Clean;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void StageAll()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public void StageAll() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
Commands.Stage(repo, "*");
|
|
Commands.Stage(repo, "*");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void Commit(string message)
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public void Commit(string message) {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
Signature signature = GetSignature(repo);
|
|
Signature signature = GetSignature(repo);
|
|
|
repo.Commit(message, signature, signature);
|
|
repo.Commit(message, signature, signature);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public string GetDiff()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
- Patch changes = repo.Diff.Compare<Patch>(
|
|
|
|
|
|
|
+ public string GetDiff() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
|
|
+ Patch patch = repo.Diff.Compare<Patch>(
|
|
|
repo.Head.Tip?.Tree,
|
|
repo.Head.Tip?.Tree,
|
|
|
DiffTargets.Index | DiffTargets.WorkingDirectory);
|
|
DiffTargets.Index | DiffTargets.WorkingDirectory);
|
|
|
- return changes?.Content ?? string.Empty;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return patch?.Content ?? string.Empty;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public string[] GetChangedFiles()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
- RepositoryStatus status = repo.RetrieveStatus();
|
|
|
|
|
- return status
|
|
|
|
|
|
|
+ public string[] GetChangedFiles() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
|
|
+ return repo.RetrieveStatus()
|
|
|
.Where(e => e.State != FileStatus.Ignored)
|
|
.Where(e => e.State != FileStatus.Ignored)
|
|
|
- .Select(e =>
|
|
|
|
|
- {
|
|
|
|
|
- var prefix = e.State switch
|
|
|
|
|
- {
|
|
|
|
|
- FileStatus.NewInWorkdir or FileStatus.NewInIndex => "A",
|
|
|
|
|
- FileStatus.DeletedFromWorkdir or FileStatus.DeletedFromIndex => "D",
|
|
|
|
|
- FileStatus.RenamedInWorkdir or FileStatus.RenamedInIndex => "R",
|
|
|
|
|
- _ when e.State.HasFlag(FileStatus.ModifiedInWorkdir)
|
|
|
|
|
- || e.State.HasFlag(FileStatus.ModifiedInIndex) => "M",
|
|
|
|
|
- _ => "?"
|
|
|
|
|
- };
|
|
|
|
|
- return $"{prefix} {e.FilePath}";
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ .Select(e => $"{GetPrefix(e.State)} {e.FilePath}")
|
|
|
.ToArray();
|
|
.ToArray();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void RestoreAll()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
- var options = new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force };
|
|
|
|
|
- repo.CheckoutPaths(repo.Head.FriendlyName, new[] { "*" }, options);
|
|
|
|
|
|
|
+ private static string GetPrefix(FileStatus state) => state switch {
|
|
|
|
|
+ FileStatus.NewInWorkdir or FileStatus.NewInIndex => "A",
|
|
|
|
|
+ FileStatus.DeletedFromWorkdir or FileStatus.DeletedFromIndex => "D",
|
|
|
|
|
+ FileStatus.RenamedInWorkdir or FileStatus.RenamedInIndex => "R",
|
|
|
|
|
+ _ when state.HasFlag(FileStatus.ModifiedInWorkdir)
|
|
|
|
|
+ || state.HasFlag(FileStatus.ModifiedInIndex) => "M",
|
|
|
|
|
+ _ => "?"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ public void RestoreAll() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
|
|
+ repo.CheckoutPaths(
|
|
|
|
|
+ repo.Head.FriendlyName,
|
|
|
|
|
+ ["*"],
|
|
|
|
|
+ new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force });
|
|
|
|
|
+
|
|
|
repo.RemoveUntrackedFiles();
|
|
repo.RemoveUntrackedFiles();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public string GetCurrentBranch()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public string GetCurrentBranch() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
return repo.Head.FriendlyName;
|
|
return repo.Head.FriendlyName;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public GitLogEntry[] GetLog(int count)
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public GitLogEntry[] GetLog(int count) {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
+
|
|
|
if (repo.Head.Tip is null)
|
|
if (repo.Head.Tip is null)
|
|
|
return [];
|
|
return [];
|
|
|
|
|
|
|
@@ -104,133 +126,88 @@ public sealed class LibGit2GitRepository : IGitRepository
|
|
|
.ToArray();
|
|
.ToArray();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public bool HasRemote()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public bool HasRemote() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
return repo.Network.Remotes.Any();
|
|
return repo.Network.Remotes.Any();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public GitSyncStatus FetchAndGetSyncStatus()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public GitSyncStatus FetchAndGetSyncStatus() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
|
|
|
if (!repo.Network.Remotes.Any())
|
|
if (!repo.Network.Remotes.Any())
|
|
|
- return new GitSyncStatus(0, 0, false);
|
|
|
|
|
|
|
+ return new(0, 0, false);
|
|
|
|
|
|
|
|
- Remote remote = repo.Network.Remotes["origin"]
|
|
|
|
|
- ?? repo.Network.Remotes.First();
|
|
|
|
|
|
|
+ Remote remote = GetRemote(repo);
|
|
|
|
|
|
|
|
- var fetchOptions = new FetchOptions();
|
|
|
|
|
- ConfigureCredentials(fetchOptions);
|
|
|
|
|
- IEnumerable<string> refSpecs = remote.FetchRefSpecs.Select(r => r.Specification);
|
|
|
|
|
- Commands.Fetch(repo, remote.Name, refSpecs, fetchOptions, null);
|
|
|
|
|
|
|
+ Commands.Fetch(
|
|
|
|
|
+ repo,
|
|
|
|
|
+ remote.Name,
|
|
|
|
|
+ remote.FetchRefSpecs.Select(r => r.Specification),
|
|
|
|
|
+ new FetchOptions { CredentialsProvider = _credentials },
|
|
|
|
|
+ null);
|
|
|
|
|
|
|
|
Branch? tracking = repo.Head.TrackedBranch;
|
|
Branch? tracking = repo.Head.TrackedBranch;
|
|
|
|
|
+
|
|
|
if (tracking is null)
|
|
if (tracking is null)
|
|
|
- {
|
|
|
|
|
- var localCount = repo.Head.Tip is not null
|
|
|
|
|
- ? repo.Commits.Count()
|
|
|
|
|
- : 0;
|
|
|
|
|
- return new GitSyncStatus(localCount, 0, true);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return new(repo.Commits.Count(), 0, true);
|
|
|
|
|
|
|
|
HistoryDivergence divergence = repo.ObjectDatabase.CalculateHistoryDivergence(
|
|
HistoryDivergence divergence = repo.ObjectDatabase.CalculateHistoryDivergence(
|
|
|
- repo.Head.Tip, tracking.Tip);
|
|
|
|
|
-
|
|
|
|
|
- var ahead = divergence.AheadBy ?? 0;
|
|
|
|
|
- var behind = divergence.BehindBy ?? 0;
|
|
|
|
|
|
|
+ repo.Head.Tip,
|
|
|
|
|
+ tracking.Tip);
|
|
|
|
|
|
|
|
- return new GitSyncStatus(ahead, behind, true);
|
|
|
|
|
|
|
+ return new(
|
|
|
|
|
+ divergence.AheadBy ?? 0,
|
|
|
|
|
+ divergence.BehindBy ?? 0,
|
|
|
|
|
+ true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void Push()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public void Push() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
|
|
|
- Remote remote = repo.Network.Remotes["origin"]
|
|
|
|
|
- ?? repo.Network.Remotes.First();
|
|
|
|
|
|
|
+ Remote remote = GetRemote(repo);
|
|
|
|
|
|
|
|
- var pushOptions = new PushOptions();
|
|
|
|
|
- ConfigureCredentials(pushOptions);
|
|
|
|
|
|
|
+ repo.Network.Push(
|
|
|
|
|
+ remote,
|
|
|
|
|
+ $"refs/heads/{repo.Head.FriendlyName}",
|
|
|
|
|
+ new PushOptions { CredentialsProvider = _credentials });
|
|
|
|
|
|
|
|
- var pushRefSpec = $"refs/heads/{repo.Head.FriendlyName}";
|
|
|
|
|
- repo.Network.Push(remote, pushRefSpec, pushOptions);
|
|
|
|
|
|
|
+ if (repo.Head.TrackedBranch is null) {
|
|
|
|
|
+ Branch remoteBranch = repo.Branches[$"{remote.Name}/{repo.Head.FriendlyName}"];
|
|
|
|
|
|
|
|
- if (repo.Head.TrackedBranch is null)
|
|
|
|
|
- {
|
|
|
|
|
- Branch? remoteBranch = repo.Branches[$"{remote.Name}/{repo.Head.FriendlyName}"];
|
|
|
|
|
- if (remoteBranch is not null)
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ if (remoteBranch != null) {
|
|
|
repo.Branches.Update(repo.Head,
|
|
repo.Branches.Update(repo.Head,
|
|
|
b => b.TrackedBranch = remoteBranch.CanonicalName);
|
|
b => b.TrackedBranch = remoteBranch.CanonicalName);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void Pull()
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public void Pull() {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
|
|
|
|
|
- var pullOptions = new PullOptions
|
|
|
|
|
- {
|
|
|
|
|
- FetchOptions = new FetchOptions()
|
|
|
|
|
- };
|
|
|
|
|
- ConfigureCredentials(pullOptions.FetchOptions);
|
|
|
|
|
-
|
|
|
|
|
- Signature signature = GetSignature(repo);
|
|
|
|
|
- Commands.Pull(repo, signature, pullOptions);
|
|
|
|
|
|
|
+ Commands.Pull(
|
|
|
|
|
+ repo,
|
|
|
|
|
+ GetSignature(repo),
|
|
|
|
|
+ new PullOptions {
|
|
|
|
|
+ FetchOptions = new FetchOptions {
|
|
|
|
|
+ CredentialsProvider = _credentials
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void AddRemote(string name, string url)
|
|
|
|
|
- {
|
|
|
|
|
- using var repo = new Repository(_repoPath);
|
|
|
|
|
|
|
+ public void AddRemote(string name, string url) {
|
|
|
|
|
+ using Repository repo = OpenRepo();
|
|
|
repo.Network.Remotes.Add(name, url);
|
|
repo.Network.Remotes.Add(name, url);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static Signature GetSignature(Repository repo)
|
|
|
|
|
- {
|
|
|
|
|
- Configuration config = repo.Config;
|
|
|
|
|
- var name = config.Get<string>("user.name")?.Value ?? "RackPeek";
|
|
|
|
|
- var email = config.Get<string>("user.email")?.Value ?? "rackpeek@local";
|
|
|
|
|
- return new Signature(name, email, DateTimeOffset.Now);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static void ConfigureCredentials(FetchOptions options)
|
|
|
|
|
- {
|
|
|
|
|
- var token = Environment.GetEnvironmentVariable("GIT_TOKEN");
|
|
|
|
|
- if (!string.IsNullOrEmpty(token))
|
|
|
|
|
- {
|
|
|
|
|
- options.CredentialsProvider = (_, _, _) =>
|
|
|
|
|
- new UsernamePasswordCredentials
|
|
|
|
|
- {
|
|
|
|
|
- Username = "git",
|
|
|
|
|
- Password = token
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static void ConfigureCredentials(PushOptions options)
|
|
|
|
|
- {
|
|
|
|
|
- var token = Environment.GetEnvironmentVariable("GIT_TOKEN");
|
|
|
|
|
- if (!string.IsNullOrEmpty(token))
|
|
|
|
|
- {
|
|
|
|
|
- options.CredentialsProvider = (_, _, _) =>
|
|
|
|
|
- new UsernamePasswordCredentials
|
|
|
|
|
- {
|
|
|
|
|
- Username = "git",
|
|
|
|
|
- Password = token
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private static string FormatRelativeDate(DateTimeOffset date)
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ private static string FormatRelativeDate(DateTimeOffset date) {
|
|
|
TimeSpan diff = DateTimeOffset.Now - date;
|
|
TimeSpan diff = DateTimeOffset.Now - date;
|
|
|
|
|
+
|
|
|
if (diff.TotalMinutes < 1) return "just now";
|
|
if (diff.TotalMinutes < 1) return "just now";
|
|
|
if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes} minutes ago";
|
|
if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes} minutes ago";
|
|
|
if (diff.TotalHours < 24) return $"{(int)diff.TotalHours} hours ago";
|
|
if (diff.TotalHours < 24) return $"{(int)diff.TotalHours} hours ago";
|
|
|
if (diff.TotalDays < 30) return $"{(int)diff.TotalDays} days ago";
|
|
if (diff.TotalDays < 30) return $"{(int)diff.TotalDays} days ago";
|
|
|
if (diff.TotalDays < 365) return $"{(int)(diff.TotalDays / 30)} months ago";
|
|
if (diff.TotalDays < 365) return $"{(int)(diff.TotalDays / 30)} months ago";
|
|
|
|
|
+
|
|
|
return $"{(int)(diff.TotalDays / 365)} years ago";
|
|
return $"{(int)(diff.TotalDays / 365)} years ago";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|