Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf2660b6b2 | |||
| 09236e826a | |||
| b9f856e716 | |||
| 1363f6ce02 | |||
| dedea9213b | |||
| 6193d30029 | |||
| 830ae2c560 | |||
| 1b09d15937 | |||
| 05d6eece99 | |||
| e6665a588b |
15 changed files with 589 additions and 8 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
|
||||||
|
<PackageVersion Include="sqlite-net-pcl" Version="1.11.285" />
|
||||||
<PackageVersion Include="System.IO.Packaging" Version="10.0.10" />
|
<PackageVersion Include="System.IO.Packaging" Version="10.0.10" />
|
||||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.10" />
|
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.10" />
|
||||||
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.6.4" />
|
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.6.4" />
|
||||||
|
|
|
||||||
162
src/ModuleCore/Git/GitManager.cs
Normal file
162
src/ModuleCore/Git/GitManager.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
namespace ModuleCore.Git;
|
||||||
|
|
||||||
|
// TODO: better name for this
|
||||||
|
public class GitManager
|
||||||
|
{
|
||||||
|
private static readonly Lazy<GitManager> GitManagerInstance = new(() => new GitManager());
|
||||||
|
public static GitManager Instance => GitManagerInstance.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Always returns a new clean instance of GitManager
|
||||||
|
/// </summary>
|
||||||
|
internal static GitManager InternalFreshInstance => new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simply <see cref="Path.DirectorySeparatorChar"/>.ToString()
|
||||||
|
/// </summary>
|
||||||
|
private static readonly string DirectorySeparator = Path.DirectorySeparatorChar.ToString();
|
||||||
|
|
||||||
|
private readonly InternalDirectory _repositories;
|
||||||
|
private readonly Lock _readWriteLock = new();
|
||||||
|
|
||||||
|
private GitManager()
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{nameof(GitManager)} init");
|
||||||
|
// Initialise the root container
|
||||||
|
_repositories = new InternalDirectory()
|
||||||
|
{
|
||||||
|
Name = DirectorySeparator,
|
||||||
|
InternalPath = DirectorySeparator
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="absoluteRepositoryLocation"></param>
|
||||||
|
/// <param name="registrationName"></param>
|
||||||
|
/// <returns>The normalised string the repository was registered against</returns>
|
||||||
|
public string RegisterRepo(string absoluteRepositoryLocation, string registrationName)
|
||||||
|
{
|
||||||
|
// Depending on the caller, it might be possible that they've scripted automatic repo registration. Because I
|
||||||
|
// don't really want to account to all the subtle ways that can be parallised, I just naively lock on every
|
||||||
|
// registration attempt. This method should be quick regardless, and I could use ConcurrentDictionary except
|
||||||
|
// that means every instance of InternalDirectory would need it and yeah nah fuck that I can just lock at the
|
||||||
|
// top level
|
||||||
|
lock (_readWriteLock)
|
||||||
|
{
|
||||||
|
var normalisedName = NormaliseNamePath(string.IsNullOrWhiteSpace(registrationName)
|
||||||
|
? new DirectoryInfo(absoluteRepositoryLocation).Name
|
||||||
|
: registrationName);
|
||||||
|
|
||||||
|
// Regardless of if we get a name or not, the fully qualified version for us
|
||||||
|
// starts with a /
|
||||||
|
var directorySegmentsFromName = NameToSegments(normalisedName);
|
||||||
|
|
||||||
|
var added = _repositories.Add(absoluteRepositoryLocation, directorySegmentsFromName);
|
||||||
|
|
||||||
|
// Not sure about this, the Add should throw any exceptions on duplicate/failures but for now I'll leave this
|
||||||
|
// here
|
||||||
|
if (added == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to register location");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalisedName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes a name and returns it as a queue of its parts, starting with a root of <see cref="DirectorySeparator"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Queue<string> NameToSegments(string name)
|
||||||
|
{
|
||||||
|
var segments = name.Split(DirectorySeparator);
|
||||||
|
|
||||||
|
return segments.Length == 1
|
||||||
|
? new Queue<string>([DirectorySeparator, name])
|
||||||
|
: new Queue<string>([DirectorySeparator, ..segments]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalises the path separators in the given string to use Path.DirectorySeparatorChar
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the folder this
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
|
public Dictionary<string, InternalDirectory> Children { get; set; } = [];
|
||||||
|
|
||||||
|
internal string InternalPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If not null, this is the absolute location of a registered git repository
|
||||||
|
/// </summary>
|
||||||
|
public string? FullRepositoryPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="absoluteRepositoryLocation"></param>
|
||||||
|
/// <param name="directorySegmentsFromName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="Exception"></exception>
|
||||||
|
internal InternalDirectory? Add(string absoluteRepositoryLocation, Queue<string> directorySegmentsFromName)
|
||||||
|
{
|
||||||
|
var topStack = directorySegmentsFromName.Dequeue();
|
||||||
|
|
||||||
|
if (topStack != Name)
|
||||||
|
{
|
||||||
|
// 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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
InternalPath = Path.Combine(InternalPath, nextSegment)
|
||||||
|
};
|
||||||
|
Children.Add(nextSegment, nextChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the next
|
||||||
|
return nextChild.Add(absoluteRepositoryLocation, directorySegmentsFromName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,4 +7,10 @@
|
||||||
<LangVersion>latestmajor</LangVersion>
|
<LangVersion>latestmajor</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
|
||||||
|
<_Parameter1>ModuleTests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Management.Automation.Runspaces;
|
using System.Management.Automation.Runspaces;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using PowershellModule.Calendar;
|
using PowershellModule.Calendar;
|
||||||
|
using PowershellModule.Git;
|
||||||
|
|
||||||
namespace PowershellHarness;
|
namespace PowershellHarness;
|
||||||
|
|
||||||
|
|
@ -36,8 +37,26 @@ class Program
|
||||||
var host = new CustomHost(Console.WindowWidth);
|
var host = new CustomHost(Console.WindowWidth);
|
||||||
var runspace = InitialisePowershellHost(host);
|
var runspace = InitialisePowershellHost(host);
|
||||||
|
|
||||||
// InvokeCommand(runspace, GetCalendarCommand.FullName);
|
host.UI.SetNextPromptChoice(3);
|
||||||
|
|
||||||
|
InvokeCommand(runspace, "New-PSDrive", [
|
||||||
|
CreateCommand("name", "git-test"),
|
||||||
|
CreateCommand("PSProvider", "GitRepo"),
|
||||||
|
CreateCommand("Root", "\\"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
InvokeCommand(runspace, "Set-Location",
|
||||||
|
[
|
||||||
|
// Technically this can just be the command but this is a bit easier
|
||||||
|
CreateCommand("path", "git-test:/")
|
||||||
|
]);
|
||||||
|
|
||||||
|
InvokeCommand(runspace, "Get-Location");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CalendarTestCommands(Runspace runspace)
|
||||||
|
{
|
||||||
|
InvokeCommand(runspace, GetCalendarCommand.FullName);
|
||||||
foreach (var day in Enum.GetValues<DayOfWeek>())
|
foreach (var day in Enum.GetValues<DayOfWeek>())
|
||||||
{
|
{
|
||||||
InvokeCommand(runspace, GetCalendarCommand.FullName, [
|
InvokeCommand(runspace, GetCalendarCommand.FullName, [
|
||||||
|
|
@ -48,11 +67,15 @@ class Program
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvokeCommand(runspace, GetCalendarCommand.FullName, [
|
InvokeCommand(runspace, GetCalendarCommand.FullName, [
|
||||||
// new CommandParameter(nameof(GetCalendarCommand.MarkedDaySymbol), "🤫"),
|
new CommandParameter(nameof(GetCalendarCommand.MarkedDaySymbol), "🤫"),
|
||||||
// new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), DayOfWeek.Saturday)
|
new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), DayOfWeek.Saturday)
|
||||||
// ]);
|
]);
|
||||||
// InvokeCommand(runspace, GetCalendarCommand.FullName, [new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), "Sunday")]);
|
InvokeCommand(runspace, GetCalendarCommand.FullName, [new CommandParameter(nameof(GetCalendarCommand.StartOfWeek), "Sunday")]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestGitProvider(Runspace runspace)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CommandParameter CreateCommand(string name, string? argument = null)
|
private static CommandParameter CreateCommand(string name, string? argument = null)
|
||||||
|
|
@ -97,6 +120,10 @@ class Program
|
||||||
var getCalendarCommand = new SessionStateCmdletEntry(GetCalendarCommand.FullName, typeof(GetCalendarCommand), null);
|
var getCalendarCommand = new SessionStateCmdletEntry(GetCalendarCommand.FullName, typeof(GetCalendarCommand), null);
|
||||||
initialSessionState.Commands.Add(getCalendarCommand);
|
initialSessionState.Commands.Add(getCalendarCommand);
|
||||||
|
|
||||||
|
|
||||||
|
var gitProvider = new SessionStateProviderEntry(GitProvider.Name, typeof(GitProvider), null);
|
||||||
|
initialSessionState.Providers.Add(gitProvider);
|
||||||
|
|
||||||
// Create a runspace from the state, open and return it
|
// Create a runspace from the state, open and return it
|
||||||
var runspace = RunspaceFactory.CreateRunspace(host, initialSessionState);
|
var runspace = RunspaceFactory.CreateRunspace(host, initialSessionState);
|
||||||
|
|
||||||
|
|
@ -114,16 +141,29 @@ class Program
|
||||||
// and this is just a debug harness so it doesn't really matter for now
|
// and this is just a debug harness so it doesn't really matter for now
|
||||||
using var pipeline = runspace.CreatePipeline();
|
using var pipeline = runspace.CreatePipeline();
|
||||||
// using var powershell = PowerShell.Create(runspace);
|
// using var powershell = PowerShell.Create(runspace);
|
||||||
|
|
||||||
|
// StringBuilder to store the output of this command including any output results (but not errors yet)
|
||||||
|
// this is just a rudimentary test and the pwsh debug profile should be used instead as it loads the module
|
||||||
|
// in a full powershell window with debugger attached. Just no automatic command running sadly.
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.Append(command);
|
||||||
|
|
||||||
var cmd = new Command(command);
|
var cmd = new Command(command);
|
||||||
if (parameters is not null)
|
if (parameters is not null)
|
||||||
{
|
{
|
||||||
foreach (var commandParameter in parameters)
|
var param = parameters.ToList();
|
||||||
|
sb.Append(' ')
|
||||||
|
.AppendJoin(' ', param.Select(x => $"-{x.Name} {x.Value}"));
|
||||||
|
|
||||||
|
foreach (var commandParameter in param)
|
||||||
{
|
{
|
||||||
cmd.Parameters.Add(commandParameter);
|
cmd.Parameters.Add(commandParameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
pipeline.Commands.Add(cmd);
|
pipeline.Commands.Add(cmd);
|
||||||
// powershell.Commands.AddCommand(cmd);
|
// powershell.Commands.AddCommand(cmd);
|
||||||
|
|
||||||
|
|
@ -133,8 +173,11 @@ class Program
|
||||||
// var results = powershell.Invoke();
|
// var results = powershell.Invoke();
|
||||||
foreach (var result in results)
|
foreach (var result in results)
|
||||||
{
|
{
|
||||||
Console.Write(result);
|
sb.AppendLine(result.ToString());
|
||||||
|
// Console.WriteLine(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(sb);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
86
src/PowershellModule/Git/GitProvider.cs
Normal file
86
src/PowershellModule/Git/GitProvider.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
using System;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Management.Automation.Provider;
|
||||||
|
|
||||||
|
namespace PowershellModule.Git;
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/powershell/scripting/developer/provider/accessdbprovidersample02?view=powershell-7.6
|
||||||
|
[CmdletProvider(Name, ProviderCapabilities.None)]
|
||||||
|
public class GitProvider : NavigationCmdletProvider
|
||||||
|
{
|
||||||
|
public const string Name = "GitRepo";
|
||||||
|
|
||||||
|
public GitProvider()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override PSDriveInfo NewDrive(PSDriveInfo drive)
|
||||||
|
{
|
||||||
|
// This is a bit of a hack to ensure that no drive is registered with a root location.
|
||||||
|
// It seems to be how PSDrives are registered for Alias providers, and we technically don't have any concept
|
||||||
|
// of a normal file system
|
||||||
|
if (drive.Root != string.Empty)
|
||||||
|
{
|
||||||
|
throw new Exception("Drive root must be an empty string");
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.NewDrive(drive);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void NewItem(string path, string itemTypeName, object newItemValue)
|
||||||
|
{
|
||||||
|
//base.NewItem(path, itemTypeName, newItemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
|
||||||
|
{
|
||||||
|
return base.RemoveDrive(drive);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void GetChildNames(string path, ReturnContainers returnContainers)
|
||||||
|
{
|
||||||
|
// TODO: get all configured repos based on a path from the underlying GitPsDriveInfo then
|
||||||
|
// output their repository names
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void GetChildItems(string path, bool recurse)
|
||||||
|
{
|
||||||
|
// TODO: get all configured repos based on a path from the underlying GitPsDriveInfo then
|
||||||
|
// output their repository locations and any other meta data I store
|
||||||
|
}
|
||||||
|
|
||||||
|
// I think this is needed to be able to cd into it
|
||||||
|
protected override bool IsItemContainer(string path)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// I think this is needed to be able to cd into it
|
||||||
|
protected override bool ItemExists(string path)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string MakePath(string parent, string child)
|
||||||
|
{
|
||||||
|
// Test to see what happens if set the location if the path matches a pretend repo registered with the name test
|
||||||
|
// The result of the test is that yes, this does work, but there's also a shit load of calls to this method as
|
||||||
|
// it traverses the entire directory path.
|
||||||
|
// This seems to just be internal powershell behaviour so as long as this is quick the first time, everything
|
||||||
|
// afterwards just always happens. But probably happens more seeing as I'm changing the location of the path
|
||||||
|
// in this current SessionState and probably doesn't happen if this were a normal path traversal
|
||||||
|
// where I don't set the location.
|
||||||
|
if (child == "test")
|
||||||
|
{
|
||||||
|
SessionState.Path.SetLocation("F:/Repos/PowershellModule/src/PowershellModule/Git");
|
||||||
|
return "F:/Repos/PowershellModule/src/PowershellModule/Git";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.MakePath(parent, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValidPath(string path)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/PowershellModule/Git/GitPsDriveInfo.cs
Normal file
12
src/PowershellModule/Git/GitPsDriveInfo.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Management.Automation;
|
||||||
|
|
||||||
|
namespace PowershellModule.Git;
|
||||||
|
|
||||||
|
public class GitPsDriveInfo : PSDriveInfo
|
||||||
|
{
|
||||||
|
protected GitPsDriveInfo(PSDriveInfo driveInfo)
|
||||||
|
: base(driveInfo)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/PowershellModule/Git/NewGitRepoCommand.cs
Normal file
114
src/PowershellModule/Git/NewGitRepoCommand.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using ModuleCore.Git;
|
||||||
|
|
||||||
|
namespace PowershellModule.Git;
|
||||||
|
|
||||||
|
[Cmdlet(VerbsCommon.New, Noun)]
|
||||||
|
public class NewGitRepoCommand : PSCmdlet
|
||||||
|
{
|
||||||
|
private const string Noun = "GitRepo";
|
||||||
|
|
||||||
|
[Parameter(
|
||||||
|
Position = 0,
|
||||||
|
ValueFromPipeline = true,
|
||||||
|
HelpMessage = "Reference name for the repo")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public NewGitRepoCommand()
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{nameof(NewGitRepoCommand)} init");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BeginProcessing()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsedGitFolderDetails? IsGitRepo(string path)
|
||||||
|
{
|
||||||
|
var ps = new ProcessStartInfo("git",
|
||||||
|
["rev-parse", "--show-toplevel"])
|
||||||
|
{
|
||||||
|
RedirectStandardOutput = 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
|
||||||
|
// special with, it'll be unhandled and powershell will handle it
|
||||||
|
var gitProcess = Process.Start(ps);
|
||||||
|
|
||||||
|
// This probably shouldn't be possible? Not really sure of the conditions where the process could be started
|
||||||
|
// but return null, but I'm going to consider that unrecoverable error territory
|
||||||
|
if (gitProcess is null)
|
||||||
|
{
|
||||||
|
throw new Exception("git failed to start");
|
||||||
|
}
|
||||||
|
|
||||||
|
gitProcess.WaitForExit();
|
||||||
|
|
||||||
|
if (!gitProcess.StandardOutput.EndOfStream)
|
||||||
|
{
|
||||||
|
var directory = gitProcess.StandardOutput.ReadToEnd();
|
||||||
|
// Gotta trim what we get as it might already have a newline character at the end
|
||||||
|
var dirInfo = new DirectoryInfo(directory.Trim());
|
||||||
|
|
||||||
|
var repoFolderInfo = new ParsedGitFolderDetails
|
||||||
|
{
|
||||||
|
Directory = dirInfo.FullName,
|
||||||
|
Folder = dirInfo.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
return repoFolderInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gitProcess.StandardError.EndOfStream)
|
||||||
|
{
|
||||||
|
var errorAsException = new Exception(gitProcess.StandardError.ReadToEnd());
|
||||||
|
WriteError(new ErrorRecord(errorAsException, "git-not-found", ErrorCategory.FromStdErr, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The directory details of the directory returned from git rev-parse --show-toplevel
|
||||||
|
/// </summary>
|
||||||
|
private class ParsedGitFolderDetails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The full path to the top level folder containing a git repository
|
||||||
|
/// </summary>
|
||||||
|
public string Directory { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last folder name of the directory
|
||||||
|
/// </summary>
|
||||||
|
public string Folder { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/PowershellModule/Git/SetGitRepoCommand.cs
Normal file
23
src/PowershellModule/Git/SetGitRepoCommand.cs
Normal file
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/PowershellModule/PostBuild.ps1
Normal file
27
src/PowershellModule/PostBuild.ps1
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes all non-core files from build output
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(ValueFromPipeline = $true, Position = 0, Mandatory = $true)]
|
||||||
|
$targetDir
|
||||||
|
)
|
||||||
|
|
||||||
|
$nativeSqliteDllLocation = "$( $targetDir )runtimes\win-x64\native\e_sqlite3.dll";
|
||||||
|
|
||||||
|
if ($false -eq (Test-Path $nativeSqliteDllLocation))
|
||||||
|
{
|
||||||
|
Write-Error "POST BUILD FAILED: Unable to locate $nativeSqliteDllLocation"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Copying '$nativeSqliteDllLocation' to '$targetDir'"
|
||||||
|
Copy-Item -Path $nativeSqliteDllLocation -Destination $targetDir
|
||||||
|
|
||||||
|
# Because we use CopyLocalLockFileAssemblies Files that are needed for the module
|
||||||
|
$allowList = @(
|
||||||
|
"ModuleCore*"
|
||||||
|
"PowershellModule*"
|
||||||
|
"*SQLite*"
|
||||||
|
)
|
||||||
|
Write-Host "Removing all non-module required files from '$targetDir'"
|
||||||
|
Get-ChildItem -Path $targetDir -exclude $allowList | Remove-Item -Recurse
|
||||||
|
|
@ -5,16 +5,22 @@
|
||||||
<AssemblyName>PowershellModule</AssemblyName>
|
<AssemblyName>PowershellModule</AssemblyName>
|
||||||
<LangVersion>latestmajor</LangVersion>
|
<LangVersion>latestmajor</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="PowerShellStandard.Library" >
|
<PackageReference Include="PowerShellStandard.Library" >
|
||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="sqlite-net-pcl" />
|
||||||
<PackageReference Include="System.Management.Automation" />
|
<PackageReference Include="System.Management.Automation" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ModuleCore\ModuleCore.csproj" />
|
<ProjectReference Include="..\ModuleCore\ModuleCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
<Exec Command="pwsh.exe -file "$(ProjectDir)PostBuild.ps1" $(TargetDir)" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
77
tests/ModuleTests/Git/AddRegistrationTests.cs
Normal file
77
tests/ModuleTests/Git/AddRegistrationTests.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
using System.Text;
|
||||||
|
using ModuleCore.Calendar;
|
||||||
|
using ModuleCore.Git;
|
||||||
|
using ModuleTests.Git.TestData;
|
||||||
|
|
||||||
|
namespace ModuleTests.Git;
|
||||||
|
|
||||||
|
public class AddRegistrationTests
|
||||||
|
{
|
||||||
|
private static readonly VerifySettings Settings;
|
||||||
|
|
||||||
|
static AddRegistrationTests()
|
||||||
|
{
|
||||||
|
Settings = new VerifySettings();
|
||||||
|
var testBaseDirectory = Path.Join(TestConstants.SnapshotFolderName, nameof(AddRegistrationTests));
|
||||||
|
|
||||||
|
Settings.UseDirectory(testBaseDirectory);
|
||||||
|
Settings.DisableDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[ClassData(typeof(AddRegistrationTestData))]
|
||||||
|
public Task BasicRepoRegistration((int testId, string path) testData)
|
||||||
|
{
|
||||||
|
Settings.UseFileName($"{nameof(BasicRepoRegistration)}_{testData.testId}");
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
|
||||||
|
var repoRegistration = gitManager.RegisterRepo("Test:/some/test/repo", testData.path);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Attempted to register: {testData.path}")
|
||||||
|
.AppendLine($"Registration result: {repoRegistration}");
|
||||||
|
|
||||||
|
return Verify(sb, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RepoRegistrationWithEmptyName()
|
||||||
|
{
|
||||||
|
Settings.UseFileName(nameof(RepoRegistrationWithEmptyName));
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
var testRepoAbsolutePath = "Test:/some/test/repo";
|
||||||
|
|
||||||
|
var emptyName = gitManager.RegisterRepo(testRepoAbsolutePath, "");
|
||||||
|
|
||||||
|
Assert.Equal("repo", emptyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RepoRegistrationWithNullName()
|
||||||
|
{
|
||||||
|
Settings.UseFileName(nameof(RepoRegistrationWithNullName));
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
var testRepoAbsolutePath = "Test:/some/test/repo";
|
||||||
|
|
||||||
|
// Name is technically not-nullable, but string is a reference type so null can be passed in so we should test
|
||||||
|
// it regardless
|
||||||
|
var nullName = gitManager.RegisterRepo(testRepoAbsolutePath, null!);
|
||||||
|
|
||||||
|
Assert.Equal("repo", nullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RepoRegistrationWithWhitespaceName()
|
||||||
|
{
|
||||||
|
Settings.UseFileName(nameof(RepoRegistrationWithWhitespaceName));
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
var testRepoAbsolutePath = "Test:/some/test/repo";
|
||||||
|
|
||||||
|
var whitespaceName = gitManager.RegisterRepo(testRepoAbsolutePath, " ");
|
||||||
|
|
||||||
|
Assert.Equal("repo", whitespaceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Attempted to register: test
|
||||||
|
Registration result: test
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Attempted to register: test\path
|
||||||
|
Registration result: test\path
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Attempted to register: other/path
|
||||||
|
Registration result: other\path
|
||||||
18
tests/ModuleTests/Git/TestData/AddRegistrationTestData.cs
Normal file
18
tests/ModuleTests/Git/TestData/AddRegistrationTestData.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace ModuleTests.Git.TestData;
|
||||||
|
|
||||||
|
public class AddRegistrationTestData : TestDataEnumerator<(int testId, string path)>
|
||||||
|
{
|
||||||
|
public AddRegistrationTestData()
|
||||||
|
{
|
||||||
|
Data = new List<string>()
|
||||||
|
{
|
||||||
|
"test",
|
||||||
|
$"test{Path.DirectorySeparatorChar}path",
|
||||||
|
$"other{Path.AltDirectorySeparatorChar}path"
|
||||||
|
}
|
||||||
|
.Select((x, i) => (i, x))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue