Compare commits

...
Author SHA1 Message Date
c48549117f chore(git-provider): Trim output when getting current branch 2026-08-24 17:35:53 +10:00
7b59b90572 chore(git-provider): Use -C when checking if current directory is a git repo
- move WriteDebug to IsGitRepo
- inline variable for SessionState.Path.CurrentLocation.Path
2026-08-24 17:10:20 +10:00
164df4ecf4 chore(git-provider): Code style 2026-08-24 16:46:35 +10:00
cf336da271 feat(git-provider): Load previous registrations, add debug hook
- add way to redirect debug output
- remove unique constraint on Location
2026-08-24 16:45:42 +10:00
2 changed files with 74 additions and 27 deletions

View file

@ -10,20 +10,16 @@ namespace ModuleCore.Git;
public class GitManager public class GitManager
{ {
private static readonly Lazy<GitManager> GitManagerInstance = new(() => new GitManager()); private static readonly Lazy<GitManager> GitManagerInstance = new(() => new GitManager());
private readonly ConcurrentDictionary<string, InternalGitRegistration> _registrations; private static Action<string>? _debugWriterDelegate;
private readonly DatabaseManager _db; private readonly DatabaseManager _db;
private readonly ConcurrentDictionary<string, InternalGitRegistration> _registrations;
private GitManager() private GitManager()
{ {
Debug.WriteLine($"{nameof(GitManager)} init");
_registrations = new ConcurrentDictionary<string, InternalGitRegistration>(); _registrations = new ConcurrentDictionary<string, InternalGitRegistration>();
_db = new DatabaseManager("git.db"); _db = new DatabaseManager("git.db");
_db.InConnection(conn => InitialiseRegistrations();
{
conn.CreateTable<InternalGitRegistration>();
});
} }
public static GitManager Instance => GitManagerInstance.Value; public static GitManager Instance => GitManagerInstance.Value;
@ -33,6 +29,40 @@ public class GitManager
/// </summary> /// </summary>
internal static GitManager InternalFreshInstance => new(); internal static GitManager InternalFreshInstance => new();
/// <summary>
/// Creates up any database tables and loads all previously saved git registrations.
/// </summary>
private void InitialiseRegistrations()
{
_debugWriterDelegate?.Invoke("Initialising GitManager from first run - this should only happen once.");
_db.InConnection(conn =>
{
var createTableResult = conn.CreateTable<InternalGitRegistration>();
if (createTableResult == CreateTableResult.Created)
{
_debugWriterDelegate?.Invoke($"Created table {InternalGitRegistration.TableName}.");
}
});
_debugWriterDelegate?.Invoke("Loading previous registrations from database.");
var registrations = _db.InConnection<List<InternalGitRegistration>>(conn =>
conn.Table<InternalGitRegistration>()
.ToList()
);
foreach (var internalGitRegistration in registrations)
{
_debugWriterDelegate?.Invoke($"Loading {internalGitRegistration.Name} ({internalGitRegistration.Id}) from database...");
if (!_registrations.TryAdd(internalGitRegistration.Name, internalGitRegistration))
{
_debugWriterDelegate?.Invoke("...failed to restore - potential duplicate name.");
}
}
}
/// <summary> /// <summary>
/// Registers a git repository based on an absolute location. If <paramref name="registrationName" /> is null or empty, /// Registers a git repository based on an absolute location. If <paramref name="registrationName" /> is null or empty,
/// the registration will use the folder name for the git repo at the top level. /// the registration will use the folder name for the git repo at the top level.
@ -55,23 +85,22 @@ public class GitManager
return _db.InConnection<string>(conn => return _db.InConnection<string>(conn =>
{ {
// Query if we already have a registration either by name or location. // Query if we already have a registration either by name. Previously we also checked by location, but I
// decided to stick with constraining to the name only, same as the key used for the dictionary.
// tbh this is a bit of a janky way to do exists when I have to pass the query in anyway, but I just didn't // tbh this is a bit of a janky way to do exists when I have to pass the query in anyway, but I just didn't
// want to do null checks and a truthy check so I wrap it in a barely-valuable method. // want to do null checks and a truthy check so I wrap it in a barely-valuable method.
var registrationExists = _db.Exists( var registrationExists = _db.Exists(
$""" $"""
SELECT 1 SELECT 1
FROM {InternalGitRegistration.TableName} FROM {InternalGitRegistration.TableName}
WHERE Name = ? OR WHERE Name = ?
Location = ?
""", """,
gitRegistration.Name, gitRegistration.Name
gitRegistration.Location
); );
if (registrationExists) if (registrationExists)
{ {
throw new Exception($"A Git repo is already registered with the name {registrationName} or location {absoluteRepositoryLocation}"); throw new Exception($"A Git repo is already registered with the name {registrationName}.");
} }
// Insert the new record // Insert the new record
@ -116,15 +145,33 @@ public class GitManager
throw new Exception($"No git repo has been registered with the name {registeredName}"); throw new Exception($"No git repo has been registered with the name {registeredName}");
} }
/// <summary>
/// Registers an output for debug output. <see cref="ClearDebugWriter" /> should be called as soon as the need for output
/// is no longer needed.
/// </summary>
/// <param name="commandRuntime"></param>
public static void SetDebugWriter(Action<string> commandRuntime)
{
_debugWriterDelegate = commandRuntime;
}
/// <summary>
/// Clears any output previously registered with <see cref="SetDebugWriter" />
/// </summary>
public static void ClearDebugWriter()
{
_debugWriterDelegate = null;
}
/// <summary> /// <summary>
/// Used for internal git registration and handles getting the current branch /// Used for internal git registration and handles getting the current branch
/// </summary> /// </summary>
[Table(TableName)] [Table(TableName)]
private class InternalGitRegistration private class InternalGitRegistration
{ {
internal const string TableName = "GitRegistration";
private string _currentBranch = string.Empty; private string _currentBranch = string.Empty;
private long _nextCheckTime; private long _nextCheckTime;
internal const string TableName = "GitRegistration";
[PrimaryKey] [PrimaryKey]
public Guid Id { get; set; } public Guid Id { get; set; }
@ -132,7 +179,6 @@ public class GitManager
[Indexed(Unique = true)] [Indexed(Unique = true)]
public string Name { get; set; } = null!; public string Name { get; set; } = null!;
[Indexed(Unique = true)]
public string Location { get; set; } = null!; public string Location { get; set; } = null!;
public string CurrentBranch => GetCurrentBranch(); public string CurrentBranch => GetCurrentBranch();
@ -184,7 +230,8 @@ public class GitManager
// Set the next check to be in the future so we don't hold up any list commands every time. // Set the next check to be in the future so we don't hold up any list commands every time.
_nextCheckTime = now.AddMinutes(15).Ticks; _nextCheckTime = now.AddMinutes(15).Ticks;
return _currentBranch; // The branch name could (will) have a newline character at the end, so we trim that off
return _currentBranch.Trim();
} }
} }
} }

View file

@ -15,20 +15,15 @@ public sealed class NewGitRepoCommand : PSCmdlet
HelpMessage = "Reference name for the repo")] HelpMessage = "Reference name for the repo")]
public string? Name { get; set; } public string? Name { get; set; }
public NewGitRepoCommand()
{
}
protected override void BeginProcessing() protected override void BeginProcessing()
{ {
var pwd = this.SessionState.Path.CurrentLocation.Path; GitManager.SetDebugWriter(WriteDebug);
WriteDebug("Checking if current directory is a git repository...");
var repoFolfder = IsGitRepo(pwd); var repoFolder = IsGitRepo(SessionState.Path.CurrentLocation.Path);
if (repoFolfder is not null) if (repoFolder is not null)
{ {
GitManager.Instance.RegisterRepo(repoFolfder.Directory, Name ?? repoFolfder.Folder); GitManager.Instance.RegisterRepo(repoFolder.Directory, Name ?? repoFolder.Folder);
} }
else else
{ {
@ -43,17 +38,20 @@ public sealed class NewGitRepoCommand : PSCmdlet
); );
} }
GitManager.ClearDebugWriter();
base.BeginProcessing(); base.BeginProcessing();
} }
private ParsedGitFolderDetails? IsGitRepo(string path) private ParsedGitFolderDetails? IsGitRepo(string path)
{ {
WriteDebug("Checking if current directory is a git repository...");
var ps = new ProcessStartInfo("git", var ps = new ProcessStartInfo("git",
["rev-parse", "--show-toplevel"]) ["-C", path, "rev-parse", "--show-toplevel"])
{ {
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
WorkingDirectory = path
}; };
// If the user doesn't have git on their path, this will throw an exception that I don't have to do anything // If the user doesn't have git on their path, this will throw an exception that I don't have to do anything
@ -75,6 +73,8 @@ public sealed class NewGitRepoCommand : PSCmdlet
// Gotta trim what we get as it might already have a newline character at the end // Gotta trim what we get as it might already have a newline character at the end
var dirInfo = new DirectoryInfo(directory.Trim()); var dirInfo = new DirectoryInfo(directory.Trim());
WriteDebug("...location is a git repo (duh).");
var repoFolderInfo = new ParsedGitFolderDetails var repoFolderInfo = new ParsedGitFolderDetails
{ {
Directory = dirInfo.FullName, Directory = dirInfo.FullName,