Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 633b35f7e9 | |||
| caa65ca7c3 | |||
| 93717b4e43 | |||
| 54d5dd25b9 | |||
| 39022e4186 | |||
| 52f7260bf4 | |||
| 48ad65e86c | |||
| 740d35d3e7 | |||
| af5c9e43b6 | |||
| a0e29619a4 | |||
| 2b3c7d0153 | |||
| 30e103f021 |
13 changed files with 333 additions and 126 deletions
64
src/ModuleCore/Database/DatabaseManager.cs
Normal file
64
src/ModuleCore/Database/DatabaseManager.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace ModuleCore.Database;
|
||||||
|
|
||||||
|
public class DatabaseManager
|
||||||
|
{
|
||||||
|
private readonly FileInfo _databaseLocation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new manager for the given database file by name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="databaseName">
|
||||||
|
/// Filename for the database with no extension. Slashes are accepted and will create directories as needed.
|
||||||
|
/// </param>
|
||||||
|
public DatabaseManager(string databaseName)
|
||||||
|
{
|
||||||
|
// Even though the xmldoc says "with no extension", we strip off any extension regardless
|
||||||
|
_databaseLocation = new FileInfo(Path.Combine(".", "data", $"{SanitiseFilename(databaseName)}.db"));
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_databaseLocation.DirectoryName!);
|
||||||
|
|
||||||
|
if (!File.Exists(_databaseLocation.FullName))
|
||||||
|
{
|
||||||
|
var file = File.Create(_databaseLocation.FullName);
|
||||||
|
file.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InConnection(Action<SQLiteConnection> dbAction)
|
||||||
|
{
|
||||||
|
using var conn = new SQLiteConnection(_databaseLocation.FullName);
|
||||||
|
dbAction(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T InConnection<T>(Func<SQLiteConnection, T> dbAction)
|
||||||
|
{
|
||||||
|
using var conn = new SQLiteConnection(_databaseLocation.FullName);
|
||||||
|
return dbAction(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a bool for the given query. Convenience method for <see cref="SQLiteConnection.ExecuteScalar"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">A query starting with SELECT 1, optionally paramaterised with ?</param>
|
||||||
|
/// <param name="args">Parameter values</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Exists(string query, params object[] args)
|
||||||
|
{
|
||||||
|
using var conn = new SQLiteConnection(_databaseLocation.FullName);
|
||||||
|
var exists = conn.ExecuteScalar<bool?>(query, args);
|
||||||
|
return exists ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes double dots from the filename and removes the file extension
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string SanitiseFilename(string filename)
|
||||||
|
{
|
||||||
|
// Honestly not really needed seeing as its just me and this isn't coming from user supplied code, but eh.
|
||||||
|
return Path.GetFileNameWithoutExtension(filename.Replace("..", string.Empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,31 @@
|
||||||
namespace ModuleCore.Git;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ModuleCore.Database;
|
||||||
|
using ModuleCore.Git.Models;
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace ModuleCore.Git;
|
||||||
|
|
||||||
// TODO: better name for this
|
// TODO: better name for this
|
||||||
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 readonly DatabaseManager _db;
|
||||||
|
|
||||||
|
private GitManager()
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"{nameof(GitManager)} init");
|
||||||
|
|
||||||
|
_registrations = new ConcurrentDictionary<string, InternalGitRegistration>();
|
||||||
|
_db = new DatabaseManager("git.db");
|
||||||
|
|
||||||
|
_db.InConnection(conn =>
|
||||||
|
{
|
||||||
|
conn.CreateTable<InternalGitRegistration>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static GitManager Instance => GitManagerInstance.Value;
|
public static GitManager Instance => GitManagerInstance.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -12,26 +34,7 @@ public class GitManager
|
||||||
internal static GitManager InternalFreshInstance => new();
|
internal static GitManager InternalFreshInstance => new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simply <see cref="Path.DirectorySeparatorChar"/>.ToString()
|
/// Registers a git repository based on an absolute location. If <paramref name="registrationName" /> is null or empty,
|
||||||
/// </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.
|
/// the registration will use the folder name for the git repo at the top level.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="absoluteRepositoryLocation"></param>
|
/// <param name="absoluteRepositoryLocation"></param>
|
||||||
|
|
@ -39,124 +42,149 @@ public class GitManager
|
||||||
/// <returns>The normalised string the repository was registered against</returns>
|
/// <returns>The normalised string the repository was registered against</returns>
|
||||||
public string RegisterRepo(string absoluteRepositoryLocation, string registrationName)
|
public string RegisterRepo(string absoluteRepositoryLocation, string registrationName)
|
||||||
{
|
{
|
||||||
// Depending on the caller, it might be possible that they've scripted automatic repo registration. Because I
|
registrationName = string.IsNullOrWhiteSpace(registrationName)
|
||||||
// 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
|
? new DirectoryInfo(absoluteRepositoryLocation).Name
|
||||||
: registrationName);
|
: registrationName;
|
||||||
|
|
||||||
// Regardless of if we get a name or not, the fully qualified version for us
|
var gitRegistration = new InternalGitRegistration
|
||||||
// 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");
|
Name = registrationName,
|
||||||
|
Location = absoluteRepositoryLocation,
|
||||||
|
Id = Guid.CreateVersion7(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return _db.InConnection<string>(conn =>
|
||||||
|
{
|
||||||
|
// Query if we already have a registration either by name or location.
|
||||||
|
// 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.
|
||||||
|
var registrationExists = _db.Exists(
|
||||||
|
$"""
|
||||||
|
SELECT 1
|
||||||
|
FROM {InternalGitRegistration.TableName}
|
||||||
|
WHERE Name = ? OR
|
||||||
|
Location = ?
|
||||||
|
""",
|
||||||
|
gitRegistration.Name,
|
||||||
|
gitRegistration.Location
|
||||||
|
);
|
||||||
|
|
||||||
|
if (registrationExists)
|
||||||
|
{
|
||||||
|
throw new Exception($"A Git repo is already registered with the name {registrationName} or location {absoluteRepositoryLocation}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalisedName;
|
// Insert the new record
|
||||||
|
conn.Insert(gitRegistration);
|
||||||
|
|
||||||
|
if (_registrations.TryAdd(registrationName, gitRegistration))
|
||||||
|
{
|
||||||
|
return registrationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This error case should be unlikely, but if a registration was removed but the registrations wasn't updated
|
||||||
|
// correctly then we'd unable to re-add a repo with the same name
|
||||||
|
throw new Exception("An error occured during registration.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GitRegistration> ListRepos()
|
||||||
|
{
|
||||||
|
return _registrations.Select(x =>
|
||||||
|
new GitRegistration
|
||||||
|
{
|
||||||
|
Name = x.Value.Name,
|
||||||
|
Location = x.Value.Location,
|
||||||
|
CurrentBranch = x.Value.CurrentBranch,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetRepo(string? registeredName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(registeredName))
|
||||||
|
{
|
||||||
|
throw new Exception("Name cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_registrations.TryGetValue(registeredName, out var registration))
|
||||||
|
{
|
||||||
|
return registration.Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"No git repo has been registered with the name {registeredName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Takes a name and returns it as a queue of its parts, starting with a root of <see cref="DirectorySeparator"/>
|
/// Used for internal git registration and handles getting the current branch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name"></param>
|
[Table(TableName)]
|
||||||
/// <returns></returns>
|
private class InternalGitRegistration
|
||||||
private Queue<string> NameToSegments(string name)
|
|
||||||
{
|
{
|
||||||
var segments = name.Split(DirectorySeparator);
|
private string _currentBranch = string.Empty;
|
||||||
|
private long _nextCheckTime;
|
||||||
|
internal const string TableName = "GitRegistration";
|
||||||
|
|
||||||
return segments.Length == 1
|
[PrimaryKey]
|
||||||
? new Queue<string>([DirectorySeparator, name])
|
public Guid Id { get; set; }
|
||||||
: new Queue<string>([DirectorySeparator, ..segments]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
[Indexed(Unique = true)]
|
||||||
/// 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 string Name { get; set; } = null!;
|
||||||
|
|
||||||
public Dictionary<string, InternalDirectory> Children { get; set; } = [];
|
[Indexed(Unique = true)]
|
||||||
|
public string Location { get; set; } = null!;
|
||||||
|
|
||||||
internal string InternalPath { get; set; }
|
public string CurrentBranch => GetCurrentBranch();
|
||||||
|
|
||||||
/// <summary>
|
// TODO: not fully decided on if I want this feature or not, but keeping it in for now
|
||||||
/// If not null, this is the absolute location of a registered git repository
|
private string GetCurrentBranch()
|
||||||
/// </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();
|
var now = DateTime.Now;
|
||||||
|
// git branch should be quick enough that even with a large number of registrations this shouldn't be that slow
|
||||||
if (topStack != Name)
|
// when doing Get-GitRepo, but regardless we still only get the current branch via git if it's been some amount
|
||||||
|
// of time since the last time we did.
|
||||||
|
if (now.Ticks < _nextCheckTime)
|
||||||
{
|
{
|
||||||
// logically it shouldn't be possible to have a value on top of the stack that _doesn't_ exist, but
|
return _currentBranch;
|
||||||
// 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
|
// use -C for the git command so we don't need to set the working directory and the git command can be run
|
||||||
// we add it to the relevant dictionary
|
// from anywhere against the appropriate location
|
||||||
if (directorySegmentsFromName.Count == 0)
|
var ps = new ProcessStartInfo("git",
|
||||||
|
["-C", Location, "branch", "--show-current"])
|
||||||
{
|
{
|
||||||
FullRepositoryPath = absoluteRepositoryLocation;
|
RedirectStandardOutput = true,
|
||||||
return this;
|
RedirectStandardError = true,
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
// 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the next
|
gitProcess.WaitForExit();
|
||||||
return nextChild.Add(absoluteRepositoryLocation, directorySegmentsFromName);
|
|
||||||
|
if (!gitProcess.StandardOutput.EndOfStream)
|
||||||
|
{
|
||||||
|
_currentBranch = gitProcess.StandardOutput.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gitProcess.StandardError.EndOfStream)
|
||||||
|
{
|
||||||
|
_currentBranch = gitProcess.StandardError.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
return _currentBranch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
src/ModuleCore/Git/Models/GitRegistration.cs
Normal file
8
src/ModuleCore/Git/Models/GitRegistration.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace ModuleCore.Git.Models;
|
||||||
|
|
||||||
|
public class GitRegistration
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Location { get; set; }
|
||||||
|
public required string CurrentBranch { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -13,4 +13,8 @@
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="sqlite-net-pcl" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
17
src/PowershellModule/Git/Commands/GetGitRepoCommand.cs
Normal file
17
src/PowershellModule/Git/Commands/GetGitRepoCommand.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Management.Automation;
|
||||||
|
using ModuleCore.Git;
|
||||||
|
|
||||||
|
namespace PowershellModule.Git.Commands;
|
||||||
|
|
||||||
|
[Cmdlet(VerbsCommon.Get, GitCommands.GitRepoNoun)]
|
||||||
|
public class ListGitRepoCommand : PSCmdlet
|
||||||
|
{
|
||||||
|
protected override void BeginProcessing()
|
||||||
|
{
|
||||||
|
var repos = GitManager.Instance.ListRepos();
|
||||||
|
|
||||||
|
WriteObject(repos);
|
||||||
|
|
||||||
|
base.BeginProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/PowershellModule/Git/Commands/GitCommands.cs
Normal file
6
src/PowershellModule/Git/Commands/GitCommands.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace PowershellModule.Git.Commands;
|
||||||
|
|
||||||
|
public class GitCommands
|
||||||
|
{
|
||||||
|
public const string GitRepoNoun = "GitRepo";
|
||||||
|
}
|
||||||
|
|
@ -4,13 +4,11 @@ using System.IO;
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
using ModuleCore.Git;
|
using ModuleCore.Git;
|
||||||
|
|
||||||
namespace PowershellModule.Git;
|
namespace PowershellModule.Git.Commands;
|
||||||
|
|
||||||
[Cmdlet(VerbsCommon.New, Noun)]
|
[Cmdlet(VerbsCommon.New, GitCommands.GitRepoNoun)]
|
||||||
public class NewGitRepoCommand : PSCmdlet
|
public sealed class NewGitRepoCommand : PSCmdlet
|
||||||
{
|
{
|
||||||
private const string Noun = "GitRepo";
|
|
||||||
|
|
||||||
[Parameter(
|
[Parameter(
|
||||||
Position = 0,
|
Position = 0,
|
||||||
ValueFromPipeline = true,
|
ValueFromPipeline = true,
|
||||||
|
|
@ -19,13 +17,12 @@ public class NewGitRepoCommand : PSCmdlet
|
||||||
|
|
||||||
public NewGitRepoCommand()
|
public NewGitRepoCommand()
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{nameof(NewGitRepoCommand)} init");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BeginProcessing()
|
protected override void BeginProcessing()
|
||||||
{
|
{
|
||||||
var pwd = this.SessionState.Path.CurrentLocation.Path;
|
var pwd = this.SessionState.Path.CurrentLocation.Path;
|
||||||
WriteObject("Checking if current directory is a git repository...");
|
WriteDebug("Checking if current directory is a git repository...");
|
||||||
|
|
||||||
var repoFolfder = IsGitRepo(pwd);
|
var repoFolfder = IsGitRepo(pwd);
|
||||||
|
|
||||||
|
|
@ -104,11 +101,11 @@ public class NewGitRepoCommand : PSCmdlet
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full path to the top level folder containing a git repository
|
/// The full path to the top level folder containing a git repository
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Directory { get; set; } = null!;
|
public string Directory { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The last folder name of the directory
|
/// The last folder name of the directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Folder { get; set; } = null!;
|
public string Folder { get; init; } = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
using System;
|
using System.Management.Automation;
|
||||||
using System.Management.Automation;
|
|
||||||
using ModuleCore.Git;
|
using ModuleCore.Git;
|
||||||
|
|
||||||
namespace PowershellModule.Git;
|
namespace PowershellModule.Git.Commands;
|
||||||
|
|
||||||
[Cmdlet(VerbsCommon.Set, Noun)]
|
// TODO: decide if I want to use this verb instead of show. Currently this implementation is under Show-GitRepo
|
||||||
|
[Cmdlet(VerbsCommon.Set, GitCommands.GitRepoNoun)]
|
||||||
public class SetGitRepoCommand : PSCmdlet
|
public class SetGitRepoCommand : PSCmdlet
|
||||||
{
|
{
|
||||||
private const string Noun = "GitRepo";
|
|
||||||
|
|
||||||
public SetGitRepoCommand()
|
public SetGitRepoCommand()
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{nameof(NewGitRepoCommand)} init");
|
|
||||||
var a = GitManager.Instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BeginProcessing()
|
protected override void BeginProcessing()
|
||||||
47
src/PowershellModule/Git/Commands/ShowGitRepoCommand.cs
Normal file
47
src/PowershellModule/Git/Commands/ShowGitRepoCommand.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
using System;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using ModuleCore.Git;
|
||||||
|
|
||||||
|
namespace PowershellModule.Git.Commands;
|
||||||
|
|
||||||
|
// TODO: decide on if I like Show to be used as the verb name. Other options I have are push/pop and open.
|
||||||
|
// Seeing as this is likely just for me currently, Show-GitRepo suits my workflow more where I'll want to quickly just
|
||||||
|
// pushd into a git repo, do whatever I want to do with it/change directories in it whatever, and then popd at the end.
|
||||||
|
// I'll also be integrating the current stack into the custom prompt whenever I get around to doing that
|
||||||
|
[Cmdlet(VerbsCommon.Show, GitCommands.GitRepoNoun)]
|
||||||
|
public class ShowGitRepoCommand : PSCmdlet
|
||||||
|
{
|
||||||
|
[Parameter(
|
||||||
|
Position = 0,
|
||||||
|
ValueFromPipeline = true,
|
||||||
|
Mandatory = true,
|
||||||
|
HelpMessage = "Reference name for the repo")]
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
[Parameter(
|
||||||
|
Mandatory = false,
|
||||||
|
HelpMessage = "Changes directory directly instead of using Set-Location")]
|
||||||
|
[Alias("NoSetLocation")]
|
||||||
|
public SwitchParameter NoStack { get; set; }
|
||||||
|
|
||||||
|
protected override void BeginProcessing()
|
||||||
|
{
|
||||||
|
var location = GitManager.Instance.GetRepo(Name);
|
||||||
|
|
||||||
|
// By default instead of doing the same as cd, we instead do pushd so a user can popd straight back to where
|
||||||
|
// they came from.
|
||||||
|
// TODO: incorporate this into the custom prompt when I develop that
|
||||||
|
if (!NoStack)
|
||||||
|
{
|
||||||
|
// Push the current location to the stack
|
||||||
|
// TODO: support named stacks. PowerShell *-Location commands support named stacks, but I don't personally
|
||||||
|
// use them myself so I haven't implemented them initially. I would like to in the future though, but right
|
||||||
|
// now it's low value to me.
|
||||||
|
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-location?view=powershell-7.6#example-4-set-the-current-location-to-a-named-stack
|
||||||
|
SessionState.Path.PushCurrentLocation(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionState.Path.SetLocation(location);
|
||||||
|
base.BeginProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ $allowList = @(
|
||||||
"ModuleCore*"
|
"ModuleCore*"
|
||||||
"PowershellModule*"
|
"PowershellModule*"
|
||||||
"*SQLite*"
|
"*SQLite*"
|
||||||
|
"data"
|
||||||
)
|
)
|
||||||
Write-Host "Removing all non-module required files from '$targetDir'"
|
Write-Host "Removing all non-module required files from '$targetDir'"
|
||||||
Get-ChildItem -Path $targetDir -exclude $allowList | Remove-Item -Recurse
|
Get-ChildItem -Path $targetDir -exclude $allowList | Remove-Item -Recurse
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
<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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,4 +74,44 @@ public class AddRegistrationTests
|
||||||
|
|
||||||
Assert.Equal("repo", whitespaceName);
|
Assert.Equal("repo", whitespaceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DuplicateRepoRegistrationShouldFail()
|
||||||
|
{
|
||||||
|
Settings.UseFileName(nameof(RepoRegistrationWithWhitespaceName));
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
var testRepoAbsolutePath = "Test:/some/test/repo";
|
||||||
|
string[] paths = ["test", "nested", "path"];
|
||||||
|
var names = (NormalSeparator: string.Join(Path.DirectorySeparatorChar, paths), AltSeparator: string.Join(Path.AltDirectorySeparatorChar, paths));
|
||||||
|
|
||||||
|
var firstRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, names.NormalSeparator);
|
||||||
|
// TODO: make nested registrations fail in both directions and test
|
||||||
|
var secondRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, Path.Combine(paths[..1]));
|
||||||
|
|
||||||
|
Assert.Equal(Path.Combine(paths), firstRegistration);
|
||||||
|
Assert.Equal(Path.Combine(paths[..1]), secondRegistration);
|
||||||
|
|
||||||
|
Assert.Throws<Exception>(() => gitManager.RegisterRepo(testRepoAbsolutePath, Path.Combine(paths[..1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DuplicateRepoRegistrationDifferentSlashShouldNotFail()
|
||||||
|
{
|
||||||
|
Settings.UseFileName(nameof(RepoRegistrationWithWhitespaceName));
|
||||||
|
|
||||||
|
var gitManager = GitManager.InternalFreshInstance;
|
||||||
|
var testRepoAbsolutePath = "Test:/some/test/repo";
|
||||||
|
string[] paths = ["test", "nested", "path"];
|
||||||
|
var names = (NormalSeparator: string.Join(Path.DirectorySeparatorChar, paths), AltSeparator: string.Join(Path.AltDirectorySeparatorChar, paths));
|
||||||
|
|
||||||
|
var firstRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, names.NormalSeparator);
|
||||||
|
// TODO: make nested registrations fail in both directions and test
|
||||||
|
var secondRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, Path.Combine(paths[..1]));
|
||||||
|
var differentPathSeparatorRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, names.AltSeparator);
|
||||||
|
|
||||||
|
Assert.Equal(Path.Combine(paths), firstRegistration);
|
||||||
|
Assert.Equal(Path.Combine(paths[..1]), secondRegistration);
|
||||||
|
Assert.Equal(names.AltSeparator, differentPathSeparatorRegistration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
Attempted to register: other/path
|
Attempted to register: other/path
|
||||||
Registration result: other\path
|
Registration result: other/path
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue