feat(git-provider): Remove concept of directory registrations and keep it simple
This commit is contained in:
parent
30e103f021
commit
2b3c7d0153
3 changed files with 45 additions and 138 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
namespace ModuleCore.Git;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace ModuleCore.Git;
|
||||||
|
|
||||||
// TODO: better name for this
|
// TODO: better name for this
|
||||||
public class GitManager
|
public class GitManager
|
||||||
|
|
@ -11,23 +13,19 @@ public class GitManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static GitManager InternalFreshInstance => new();
|
internal static GitManager InternalFreshInstance => new();
|
||||||
|
|
||||||
/// <summary>
|
private class GitRegistration
|
||||||
/// Simply <see cref="Path.DirectorySeparatorChar"/>.ToString()
|
{
|
||||||
/// </summary>
|
public required string Name { get; set; }
|
||||||
private static readonly string DirectorySeparator = Path.DirectorySeparatorChar.ToString();
|
public required string Location { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private readonly InternalDirectory _repositories;
|
private readonly ConcurrentDictionary<string, GitRegistration> _registrations;
|
||||||
private readonly Lock _readWriteLock = new();
|
|
||||||
|
|
||||||
private GitManager()
|
private GitManager()
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{nameof(GitManager)} init");
|
Console.WriteLine($"{nameof(GitManager)} init");
|
||||||
// Initialise the root container
|
|
||||||
_repositories = new InternalDirectory()
|
_registrations = new ConcurrentDictionary<string, GitRegistration>();
|
||||||
{
|
|
||||||
Name = DirectorySeparator,
|
|
||||||
InternalPath = DirectorySeparator
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,131 +37,19 @@ 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
|
if (_registrations.TryAdd(registrationName, new GitRegistration()
|
||||||
// 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,
|
||||||
|
}))
|
||||||
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 registrationName;
|
||||||
|
|
||||||
return segments.Length == 1
|
|
||||||
? new Queue<string>([DirectorySeparator, name])
|
|
||||||
: new Queue<string>([DirectorySeparator, ..segments]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
throw new Exception($"Git repo already registered with the name {registrationName}");
|
||||||
/// 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)
|
|
||||||
{
|
|
||||||
// If this is a new registration, set the FullRepositoryPath and return, otherwise we've got a duplicate
|
|
||||||
// entry and we throw
|
|
||||||
if (FullRepositoryPath == null)
|
|
||||||
{
|
|
||||||
FullRepositoryPath = absoluteRepositoryLocation;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($"Registration already exists for {InternalPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +90,28 @@ public class AddRegistrationTests
|
||||||
var secondRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, Path.Combine(paths[..1]));
|
var secondRegistration = gitManager.RegisterRepo(testRepoAbsolutePath, Path.Combine(paths[..1]));
|
||||||
|
|
||||||
Assert.Equal(Path.Combine(paths), firstRegistration);
|
Assert.Equal(Path.Combine(paths), firstRegistration);
|
||||||
|
Assert.Equal(Path.Combine(paths[..1]), secondRegistration);
|
||||||
|
|
||||||
Assert.Throws<Exception>(() => gitManager.RegisterRepo(testRepoAbsolutePath, names.AltSeparator));
|
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