From f62f2966b164e95a67313b6cd0dc4528b8ac1bd8 Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Tue, 2 Apr 2024 23:31:00 +0200 Subject: [PATCH 1/3] support bare repositories --- ...t_variables_repository.generated.source.md | 21 +++++++++++++++++++ .../ActionMenu/Context/RepositoryFunctions.cs | 14 +++++++++++++ .../RepoMCodeGen.generated.cs | 1 + src/RepoM.Api/Git/DefaultRepositoryReader.cs | 11 ++++++++-- src/RepoM.Api/Git/Repository.cs | 3 +++ .../Repository/IRepository.cs | 2 ++ .../Internal/EverythingGitRepositoryFinder.cs | 2 +- .../DummyRepository.cs | 2 ++ 8 files changed, 53 insertions(+), 3 deletions(-) diff --git a/docs_new/mdsource/script_variables_repository.generated.source.md b/docs_new/mdsource/script_variables_repository.generated.source.md index 166f3eb4..71c7bd68 100644 --- a/docs_new/mdsource/script_variables_repository.generated.source.md +++ b/docs_new/mdsource/script_variables_repository.generated.source.md @@ -6,6 +6,7 @@ This module contains the following methods, variables and/or constants: - [`repository.branch`](#branch) - [`repository.branches`](#branches) +- [`repository.is_bare`](#is_bare) - [`repository.linux_path`](#linux_path) - [`repository.local_branches`](#local_branches) - [`repository.location`](#location) @@ -34,6 +35,26 @@ Gets the current branch of the repository The name of the current branch. +## is_bare + +`repository.is_bare` + +Gets the information if the repository is a bare repository. + +### Returns + +If the repository is cloned as bare repository. + +### Example + +#### Usage + + +``` +repository.is_bare +``` + + ## linux_path `repository.linux_path` diff --git a/src/RepoM.ActionMenu.Core/ActionMenu/Context/RepositoryFunctions.cs b/src/RepoM.ActionMenu.Core/ActionMenu/Context/RepositoryFunctions.cs index 44ee3078..14e0a44d 100644 --- a/src/RepoM.ActionMenu.Core/ActionMenu/Context/RepositoryFunctions.cs +++ b/src/RepoM.ActionMenu.Core/ActionMenu/Context/RepositoryFunctions.cs @@ -33,6 +33,20 @@ public RepositoryFunctions(IRepository repository) [ActionMenuContextMember("name")] public string Name => _repository.Name; + /// + /// Gets the information if the repository is a bare repository. + /// + /// If the repository is cloned as bare repository. + /// + /// + /// + /// repository.is_bare + /// + /// + [ActionMenuContextMember("is_bare")] + public bool IsBare => _repository.IsBare; + + /// /// Gets the path of the repository. The path is windows or linux based (depending on the running OS) and does NOT end with a (back)slash. /// diff --git a/src/RepoM.ActionMenu.Core/RepoMCodeGen.generated.cs b/src/RepoM.ActionMenu.Core/RepoMCodeGen.generated.cs index 52326f5d..cbf5c66d 100644 --- a/src/RepoM.ActionMenu.Core/RepoMCodeGen.generated.cs +++ b/src/RepoM.ActionMenu.Core/RepoMCodeGen.generated.cs @@ -31,6 +31,7 @@ protected sealed override void RegisterFunctions() { RegisterConstant("branch", CurrentBranch); RegisterConstant("branches", Branches); + RegisterConstant("is_bare", IsBare); RegisterConstant("linux_path", LinuxPath); RegisterConstant("local_branches", LocalBranches); RegisterConstant("location", Location); diff --git a/src/RepoM.Api/Git/DefaultRepositoryReader.cs b/src/RepoM.Api/Git/DefaultRepositoryReader.cs index 04a49b79..7672c2d7 100644 --- a/src/RepoM.Api/Git/DefaultRepositoryReader.cs +++ b/src/RepoM.Api/Git/DefaultRepositoryReader.cs @@ -85,10 +85,16 @@ public DefaultRepositoryReader(IRepositoryTagsFactory resolver, ILogger logger) try { using var repo = new LibGit2Sharp.Repository(repoPath); - RepositoryStatus status = repo.RetrieveStatus(); - var workingDirectory = new DirectoryInfo(repo.Info.WorkingDirectory); + RepositoryStatus? status = null; + var workingDirectory = new DirectoryInfo(repoPath); + if (!repo.Info.IsBare) + { + status = repo.RetrieveStatus(); + workingDirectory = new DirectoryInfo(repo.Info.WorkingDirectory); + } + if (string.IsNullOrWhiteSpace(workingDirectory.Parent?.FullName)) { _logger.LogError("WorkingDirectory.Parent.Fullname was null or empty for repository found in '{Path}'. Return null", repoPath); @@ -99,6 +105,7 @@ public DefaultRepositoryReader(IRepositoryTagsFactory resolver, ILogger logger) var repository = new Repository(workingDirectory.FullName) { + IsBare = repo.Info.IsBare, Name = workingDirectory.Name, Location = workingDirectory.Parent.FullName, Branches = repo.Branches.Select(b => b.FriendlyName).ToArray(), diff --git a/src/RepoM.Api/Git/Repository.cs b/src/RepoM.Api/Git/Repository.cs index d839901b..87658aa0 100644 --- a/src/RepoM.Api/Git/Repository.cs +++ b/src/RepoM.Api/Git/Repository.cs @@ -12,6 +12,7 @@ public class Repository : IRepository public Repository(string path) { + IsBare = false; Name = string.Empty; Branches = Array.Empty(); LocalBranches = Array.Empty(); @@ -44,6 +45,8 @@ public override int GetHashCode() return Path.GetHashCode(); } + public bool IsBare { get; init; } + public string Name { get; set; } public string Path { get; } diff --git a/src/RepoM.Core.Plugin/Repository/IRepository.cs b/src/RepoM.Core.Plugin/Repository/IRepository.cs index 36aa2492..2c2f161b 100644 --- a/src/RepoM.Core.Plugin/Repository/IRepository.cs +++ b/src/RepoM.Core.Plugin/Repository/IRepository.cs @@ -9,6 +9,8 @@ public interface IRepository { string Name { get; } + bool IsBare { get; } + string Path { get; } string WindowsPath { get; } diff --git a/src/RepoM.Plugin.EverythingFileSearch/Internal/EverythingGitRepositoryFinder.cs b/src/RepoM.Plugin.EverythingFileSearch/Internal/EverythingGitRepositoryFinder.cs index 018fb4b4..e7d78802 100644 --- a/src/RepoM.Plugin.EverythingFileSearch/Internal/EverythingGitRepositoryFinder.cs +++ b/src/RepoM.Plugin.EverythingFileSearch/Internal/EverythingGitRepositoryFinder.cs @@ -16,7 +16,7 @@ public EverythingGitRepositoryFinder(IPathSkipper pathSkipper) public List Find(string root, Action? onFoundAction) { - const string SEARCH = "file: .git\\HEAD"; + const string SEARCH = "file:\"HEAD\" endwith:\"HEAD\" startwith:\"HEAD\""; var result = Everything64Api.Search($"\"{root}\" {SEARCH}") .Where(item => !string.IsNullOrWhiteSpace(item)) diff --git a/tests/RepoM.ActionMenu.Core.Tests/DummyRepository.cs b/tests/RepoM.ActionMenu.Core.Tests/DummyRepository.cs index 532fa082..e94ae6b1 100644 --- a/tests/RepoM.ActionMenu.Core.Tests/DummyRepository.cs +++ b/tests/RepoM.ActionMenu.Core.Tests/DummyRepository.cs @@ -18,6 +18,8 @@ public class DummyRepository : IRepository public string Name => "dummy name"; + public bool IsBare => false; + public string Path => WindowsPath; public string WindowsPath=> @"C:\Projects\Github\RepoM"; From d1ccc22b90ac60d2a25290e5138507b534a6163c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 2 Apr 2024 21:31:44 +0000 Subject: [PATCH 2/3] Docs changes --- .../script_variables_repository.generated.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs_new/script_variables_repository.generated.md b/docs_new/script_variables_repository.generated.md index 166f3eb4..71c7bd68 100644 --- a/docs_new/script_variables_repository.generated.md +++ b/docs_new/script_variables_repository.generated.md @@ -6,6 +6,7 @@ This module contains the following methods, variables and/or constants: - [`repository.branch`](#branch) - [`repository.branches`](#branches) +- [`repository.is_bare`](#is_bare) - [`repository.linux_path`](#linux_path) - [`repository.local_branches`](#local_branches) - [`repository.location`](#location) @@ -34,6 +35,26 @@ Gets the current branch of the repository The name of the current branch. +## is_bare + +`repository.is_bare` + +Gets the information if the repository is a bare repository. + +### Returns + +If the repository is cloned as bare repository. + +### Example + +#### Usage + + +``` +repository.is_bare +``` + + ## linux_path `repository.linux_path` From 958d622576f4a65ac21b9f77b5ce9b7cc4ebdde0 Mon Sep 17 00:00:00 2001 From: Coen van den Munckhof Date: Wed, 3 Apr 2024 18:52:23 +0200 Subject: [PATCH 3/3] fix bug --- src/RepoM.Api/Git/DefaultRepositoryMonitor.cs | 25 +++++--- .../Git/DefaultRepositoryObserver.cs | 16 ++--- .../Git/DefaultRepositoryReaderTests.cs | 60 +++++++++++++++++++ 3 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 tests/RepoM.Api.Tests/Git/DefaultRepositoryReaderTests.cs diff --git a/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs b/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs index 79f6987b..16b045af 100644 --- a/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs +++ b/src/RepoM.Api/Git/DefaultRepositoryMonitor.cs @@ -246,17 +246,22 @@ public bool IsPinned(IRepository repository) private void CreateRepositoryObserver(IRepository repo, string path) { - if (!_repositoryObservers.ContainsKey(path)) + if (_repositoryObservers.TryGetValue(path, out IRepositoryObserver? observer)) { - IRepositoryObserver observer = _repositoryObserverFactory.Create(); - observer.Setup(repo, DelayGitStatusAfterFileOperationMilliseconds); - _repositoryObservers.Add(path, observer); + return; + } + + observer = _repositoryObserverFactory.Create(); - observer.OnChange += OnRepositoryObserverChange; + if (!_repositoryObservers.TryAdd(path, observer)) + { + observer.Dispose(); + return; } - _repositoryObservers[path].Start(); - _logger.LogDebug("{Method} - repo {Repo}, path: {Path} (total length: {RepositoryObserversLength})", nameof(CreateRepositoryObserver), repo.Name, path, _repositoryObservers.Count); + observer.Setup(repo, DelayGitStatusAfterFileOperationMilliseconds); + observer.OnChange += OnRepositoryObserverChange; + observer.Start(); } private void OnRepositoryChangeDetected(IRepository repo) @@ -303,8 +308,12 @@ private void DestroyRepositoryObserver(string path) return; } + if (!_repositoryObservers.Remove(path)) + { + return; + } + observer.Stop(); - _repositoryObservers.Remove(path); } private void OnRepositoryDeletionDetected(string repoPath) diff --git a/src/RepoM.Api/Git/DefaultRepositoryObserver.cs b/src/RepoM.Api/Git/DefaultRepositoryObserver.cs index bb9bcca3..66f57982 100644 --- a/src/RepoM.Api/Git/DefaultRepositoryObserver.cs +++ b/src/RepoM.Api/Git/DefaultRepositoryObserver.cs @@ -67,14 +67,16 @@ public void Stop() public void Dispose() { - if (_watcher != null) + IFileSystemWatcher? watcher = _watcher; + _watcher = null; + + if (watcher != null) { - _watcher.Created -= FileSystemUpdated; - _watcher.Changed -= FileSystemUpdated; - _watcher.Deleted -= FileSystemUpdated; - _watcher.Renamed -= FileSystemUpdated; - _watcher.Dispose(); - _watcher = null; + watcher.Created -= FileSystemUpdated; + watcher.Changed -= FileSystemUpdated; + watcher.Deleted -= FileSystemUpdated; + watcher.Renamed -= FileSystemUpdated; + watcher.Dispose(); } LibGit2Sharp.Repository? gr = _gitRepo; diff --git a/tests/RepoM.Api.Tests/Git/DefaultRepositoryReaderTests.cs b/tests/RepoM.Api.Tests/Git/DefaultRepositoryReaderTests.cs new file mode 100644 index 00000000..8a918c3a --- /dev/null +++ b/tests/RepoM.Api.Tests/Git/DefaultRepositoryReaderTests.cs @@ -0,0 +1,60 @@ +namespace RepoM.Api.Tests.Git; + +using System; +using System.IO; +using System.Threading.Tasks; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using RepoM.Api.Git; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; +using Xunit; + +public class DefaultRepositoryReaderTests +{ + private readonly IRepositoryTagsFactory _resolver = A.Fake(); + private readonly ILogger _logger = A.Dummy(); + + [Fact] + public void Ctor_ShouldThrow_WhenArgumentNull() + { + // arrange + + // act + Func act1 = () => new DefaultRepositoryReader(_resolver, null!); + Func act2 = () => new DefaultRepositoryReader(null!, _logger); + + // assert + act1.Should().Throw(); + act2.Should().Throw(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task ReadRepositoryAsync_ShouldReturnNull_WhenPathIsNullOrEmpty(string? path) + { + // arrange + var sut = new DefaultRepositoryReader(_resolver, _logger); + + // act + Repository? result = await sut.ReadRepositoryAsync(path!); + + // assert + result.Should().BeNull(); + } + + [Fact] + public async Task ReadRepositoryAsync_ShouldReturnNull_WhenPathDoesNotExist() + { + // arrange + var path = Path.Combine("C:", Guid.NewGuid().ToString(), Guid.NewGuid().ToString()); + var sut = new DefaultRepositoryReader(_resolver, _logger); + + // act + Repository? result = await sut.ReadRepositoryAsync(path); + + // assert + result.Should().BeNull(because:$"Path '{path}' is generated and should not exists."); + } +} \ No newline at end of file