From b9f856e716ba6ae36f394f1ad661f747601d7291 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 10 Aug 2026 14:54:15 +1000 Subject: [PATCH] feat(git-provider): First pass of creating directories from New-GitRepo command - move GitManager to ModuleCore - refactor SetGitRepoCommand to own file --- src/ModuleCore/Git/GitManager.cs | 131 ++++++++++++++++++ src/PowershellModule/Git/NewGitRepoCommand.cs | 55 +++----- src/PowershellModule/Git/SetGitRepoCommand.cs | 23 +++ 3 files changed, 173 insertions(+), 36 deletions(-) create mode 100644 src/ModuleCore/Git/GitManager.cs create mode 100644 src/PowershellModule/Git/SetGitRepoCommand.cs diff --git a/src/ModuleCore/Git/GitManager.cs b/src/ModuleCore/Git/GitManager.cs new file mode 100644 index 0000000..7dbfe8a --- /dev/null +++ b/src/ModuleCore/Git/GitManager.cs @@ -0,0 +1,131 @@ +namespace ModuleCore.Git; + +// TODO: better name for this +public class GitManager +{ + private static readonly Lazy GitManagerInstance = new(() => new GitManager()); + public static GitManager Instance => GitManagerInstance.Value; + + /// + /// Simply .ToString() + /// + private static readonly string DirectorySeparator = Path.DirectorySeparatorChar.ToString(); + + private readonly InternalDirectory _repositories; + + private GitManager() + { + Console.WriteLine($"{nameof(GitManager)} init"); + // Initialise the root container + _repositories = new InternalDirectory() + { + Name = DirectorySeparator, + }; + } + + /// + /// Registers a git repository based on an absolute location. If is null or empty, + /// the registration will use the folder name for the git repo at the top level. + /// + /// + /// + /// + public void RegisterRepo(string absoluteRepositoryLocation, string registrationName) + { + // Regardless of if we get a name or not, the fully qualified version for us + // starts with a + var directorySegmentsFromName = NameToSegments( + string.IsNullOrWhiteSpace(registrationName) + ? new DirectoryInfo(absoluteRepositoryLocation).Name + : registrationName + ); + + _repositories.Add(absoluteRepositoryLocation, directorySegmentsFromName); + } + + /// + /// Takes a name and returns it as a queue of its parts, starting with a root of + /// + /// + /// + private Queue NameToSegments(string name) + { + var segments = NormaliseNamePath(name).Split(DirectorySeparator); + + return segments.Length == 1 + ? new Queue([DirectorySeparator, name]) + : new Queue([DirectorySeparator, ..segments]); + } + + /// + /// Normalises the path separators in the given string to use Path.DirectorySeparatorChar + /// + /// + /// + private string NormaliseNamePath(string name) + { + // Feels a bit hacky, but this will actually normalise a path to a valid form. So if the input is + // some/directory/paths, Path.GetRelativePath will normalise it to some\directory\paths, relative to ./ + // which is kind of handy but I also just wish there was a Path method that would do this for me. I know that + // the whole point of Path is that it's based on a file system, but file systems can also be arbitrary and not + // always be drive rooted. + // Either way, this works and saves me having to reimplement a worse method when it's more important that users + // are able to use file paths in whatever form they prefer, which means we leverage the internal implementation + // in a weird way. + return Path.GetRelativePath("./", name); + } + + private class InternalDirectory + { + /// + /// Name of the folder this + /// + public string Name { get; set; } = null!; + + public Dictionary Children { get; set; } = []; + + /// + /// If not null, this is the absolute location of a registered git repository + /// + public string? FullRepositoryPath { get; set; } + + internal void Add(string absoluteRepositoryLocation, Queue directorySegmentsFromName) + { + var topStack = directorySegmentsFromName.Dequeue(); + + if (topStack == Name) + { + // We're at the end of the directory segments so we can safely say we're at the end of the tree so + // we add it to the relevant dictionary + if (directorySegmentsFromName.Count == 0) + { + FullRepositoryPath = absoluteRepositoryLocation; + //Children.Add(topStack, directory); + } + else + { + var nextSegment = directorySegmentsFromName.Peek(); + // Attempt to get the next level of the directory. If we don't have a key entry, create one + if (!Children.TryGetValue(nextSegment, out var nextChild)) + { + nextChild = new InternalDirectory() + { + Name = nextSegment, + }; + Children.Add(nextSegment, nextChild); + } + + // add the next + nextChild.Add(absoluteRepositoryLocation, directorySegmentsFromName); + } + } + else + { + // logically it shouldn't be possible to have a value on top of the stack that _doesn't_ exist, but + // just incase we throw as this should only happen if an Add is attempted on the root and the queue was + // not correctly rooted to / + throw new Exception($"Directory segment does not seem to exist: {topStack}"); + } + } + } +} \ No newline at end of file diff --git a/src/PowershellModule/Git/NewGitRepoCommand.cs b/src/PowershellModule/Git/NewGitRepoCommand.cs index 3486a91..2b4bf6a 100644 --- a/src/PowershellModule/Git/NewGitRepoCommand.cs +++ b/src/PowershellModule/Git/NewGitRepoCommand.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.IO; using System.Management.Automation; -using SQLite; +using ModuleCore.Git; namespace PowershellModule.Git; @@ -12,27 +12,40 @@ public class NewGitRepoCommand : PSCmdlet private const string Noun = "GitRepo"; [Parameter( - Mandatory = true, Position = 0, ValueFromPipeline = true, HelpMessage = "Reference name for the repo")] - public string Name { get; set; } + public string? Name { get; set; } public NewGitRepoCommand() { Console.WriteLine($"{nameof(NewGitRepoCommand)} init"); - var gm = GitManager.Instance; } protected override void BeginProcessing() { - //using var conn = new SQLiteConnection("./test.db"); - var pwd = this.SessionState.Path.CurrentLocation.Path; WriteObject("Checking if current directory is a git repository..."); var repoFolfder = IsGitRepo(pwd); + if (repoFolfder is not null) + { + GitManager.Instance.RegisterRepo(repoFolfder.Directory, Name ?? repoFolfder.Folder); + } + else + { + // Not sure how we'd hit this path, but in case we do, show some sort of error. + // TODO: I should probably have IsGitRepo throw instead so I can get the location it tried in the stack track + WriteError(new ErrorRecord( + new Exception("Unable to register repo - failed to parse git repo location"), + "git-parse-failed", + ErrorCategory.InvalidData, + null + ) + ); + } + base.BeginProcessing(); } @@ -98,34 +111,4 @@ public class NewGitRepoCommand : PSCmdlet /// public string Folder { get; set; } = null!; } -} - -[Cmdlet(VerbsCommon.Set, Noun)] -public class SetGitRepoCommand : PSCmdlet -{ - private const string Noun = "GitRepo"; - - public SetGitRepoCommand() - { - Console.WriteLine($"{nameof(NewGitRepoCommand)} init"); - var a = GitManager.Instance; - } - - protected override void BeginProcessing() - { - SessionState.Path.SetLocation("F:/Repos/PowershellModule/src/PowershellModule/Git"); - base.BeginProcessing(); - } -} - -// TODO: better name for this -public class GitManager -{ - private static readonly Lazy GitManagerInstance = new(() => new GitManager()); - public static GitManager Instance => GitManagerInstance.Value; - - private GitManager() - { - Console.WriteLine($"{nameof(GitManager)} init"); - } } \ No newline at end of file diff --git a/src/PowershellModule/Git/SetGitRepoCommand.cs b/src/PowershellModule/Git/SetGitRepoCommand.cs new file mode 100644 index 0000000..9d6a7f1 --- /dev/null +++ b/src/PowershellModule/Git/SetGitRepoCommand.cs @@ -0,0 +1,23 @@ +using System; +using System.Management.Automation; +using ModuleCore.Git; + +namespace PowershellModule.Git; + +[Cmdlet(VerbsCommon.Set, Noun)] +public class SetGitRepoCommand : PSCmdlet +{ + private const string Noun = "GitRepo"; + + public SetGitRepoCommand() + { + Console.WriteLine($"{nameof(NewGitRepoCommand)} init"); + var a = GitManager.Instance; + } + + protected override void BeginProcessing() + { + SessionState.Path.SetLocation("F:/Repos/PowershellModule/src/PowershellModule/Git"); + base.BeginProcessing(); + } +} \ No newline at end of file