diff --git a/RepoZ.Api.Common/Git/FileRepositoryStore.cs b/RepoZ.Api.Common/Git/FileRepositoryStore.cs index d7f8a155..e5b2768d 100644 --- a/RepoZ.Api.Common/Git/FileRepositoryStore.cs +++ b/RepoZ.Api.Common/Git/FileRepositoryStore.cs @@ -20,13 +20,11 @@ protected FileRepositoryStore(IErrorHandler errorHandler) public abstract string GetFileName(); - public IEnumerable Get() + public IEnumerable Get(string file) { if (!UseFilePersistence) - return new string[0]; - - string file = GetFileName(); - + return Array.Empty(); + if (File.Exists(file)) { try @@ -39,7 +37,13 @@ public IEnumerable Get() } } - return new string[0]; + return Array.Empty(); + } + + public IEnumerable Get() + { + string file = GetFileName(); + return Get(file); } public void Set(IEnumerable paths) diff --git a/RepoZ.Api.Common/Git/RepositoryActions/DefaultRepositoryActionConfigurationStore.cs b/RepoZ.Api.Common/Git/RepositoryActions/DefaultRepositoryActionConfigurationStore.cs index 7eb2aa1a..15a961d2 100644 --- a/RepoZ.Api.Common/Git/RepositoryActions/DefaultRepositoryActionConfigurationStore.cs +++ b/RepoZ.Api.Common/Git/RepositoryActions/DefaultRepositoryActionConfigurationStore.cs @@ -5,20 +5,52 @@ using System.IO; using System.Linq; using System.Reflection; +using RepoZ.Api.Git; namespace RepoZ.Api.Common.Git { public class DefaultRepositoryActionConfigurationStore : FileRepositoryStore, IRepositoryActionConfigurationStore { + private const string REPOSITORY_ACTIONS_FILENAME = "RepositoryActions.json"; private object _lock = new object(); + private readonly IAppDataPathProvider _appDataPathProvider; - public DefaultRepositoryActionConfigurationStore(IErrorHandler errorHandler, IAppDataPathProvider appDataPathProvider) + public DefaultRepositoryActionConfigurationStore(IErrorHandler errorHandler, + IAppDataPathProvider appDataPathProvider) : base(errorHandler) { - AppDataPathProvider = appDataPathProvider ?? throw new ArgumentNullException(nameof(appDataPathProvider)); + _appDataPathProvider = appDataPathProvider ?? throw new ArgumentNullException(nameof(appDataPathProvider)); } - public override string GetFileName() => Path.Combine(AppDataPathProvider.GetAppDataPath(), "RepositoryActions.json"); + public override string GetFileName() + { + return Path.Combine(_appDataPathProvider.GetAppDataPath(), REPOSITORY_ACTIONS_FILENAME); + } + + public RepositoryActionConfiguration LoadRepositoryConfiguration(Repository repo) + { + var file = Path.Combine(repo.Path, ".git", REPOSITORY_ACTIONS_FILENAME); + if (File.Exists(file)) + { + var result = LoadRepositoryActionConfiguration(file); + if (result.State == RepositoryActionConfiguration.LoadState.Ok) + { + return result; + } + } + + file = Path.Combine(repo.Path, REPOSITORY_ACTIONS_FILENAME); + if (File.Exists(file)) + { + var result = LoadRepositoryActionConfiguration(file); + if (result.State == RepositoryActionConfiguration.LoadState.Ok) + { + return result; + } + } + + return null; + } public void Preload() { @@ -34,32 +66,43 @@ public void Preload() } } - try - { - var lines = Get()?.ToList() ?? new List(); - var json = string.Join(Environment.NewLine, lines.Select(RemoveComment)); - RepositoryActionConfiguration = JsonConvert.DeserializeObject(json) ?? new RepositoryActionConfiguration(); - RepositoryActionConfiguration.State = RepositoryActionConfiguration.LoadState.Ok; - } - catch (Exception ex) + RepositoryActionConfiguration = LoadRepositoryActionConfiguration(GetFileName()); + } + } + + public RepositoryActionConfiguration LoadRepositoryActionConfiguration(string filename) + { + try + { + var lines = Get(filename)?.ToList() ?? new List(); + var json = string.Join(Environment.NewLine, lines.Select(RemoveComment)); + var repositoryActionConfiguration = JsonConvert.DeserializeObject(json) ?? new RepositoryActionConfiguration(); + repositoryActionConfiguration.State = RepositoryActionConfiguration.LoadState.Ok; + return repositoryActionConfiguration; + } + catch (Exception ex) + { + return new RepositoryActionConfiguration { - RepositoryActionConfiguration = new RepositoryActionConfiguration(); - RepositoryActionConfiguration.State = RepositoryActionConfiguration.LoadState.Error; - RepositoryActionConfiguration.LoadError = ex.Message; - } + State = RepositoryActionConfiguration.LoadState.Error, + LoadError = ex.Message + }; } } private bool TryCopyDefaultJsonFile() { - var defaultFile = Path.Combine(AppDataPathProvider.GetAppResourcesPath(), "RepositoryActions.json"); + var defaultFile = Path.Combine(_appDataPathProvider.GetAppResourcesPath(), REPOSITORY_ACTIONS_FILENAME); var targetFile = GetFileName(); try { File.Copy(defaultFile, targetFile); } - catch { /* lets ignore errors here, we just want to know if if worked or not by checking the file existence */ } + catch + { + /* lets ignore errors here, we just want to know if if worked or not by checking the file existence */ + } return File.Exists(targetFile); } @@ -71,7 +114,5 @@ private string RemoveComment(string line) } public RepositoryActionConfiguration RepositoryActionConfiguration { get; private set; } - - public IAppDataPathProvider AppDataPathProvider { get; } } } diff --git a/RepoZ.Api.Common/Git/RepositoryActions/IRepositoryActionConfigurationStore.cs b/RepoZ.Api.Common/Git/RepositoryActions/IRepositoryActionConfigurationStore.cs index 58ab8b2b..babc10a2 100644 --- a/RepoZ.Api.Common/Git/RepositoryActions/IRepositoryActionConfigurationStore.cs +++ b/RepoZ.Api.Common/Git/RepositoryActions/IRepositoryActionConfigurationStore.cs @@ -1,9 +1,15 @@ -namespace RepoZ.Api.Common.Git +using RepoZ.Api.Git; + +namespace RepoZ.Api.Common.Git { public interface IRepositoryActionConfigurationStore { void Preload(); RepositoryActionConfiguration RepositoryActionConfiguration { get; } + + RepositoryActionConfiguration LoadRepositoryConfiguration(Repository repo); + + RepositoryActionConfiguration LoadRepositoryActionConfiguration(string filename); } } \ No newline at end of file diff --git a/RepoZ.Api.Common/Git/RepositoryActions/RepositoryActionConfiguration.cs b/RepoZ.Api.Common/Git/RepositoryActions/RepositoryActionConfiguration.cs index 8086ffe8..bd59183a 100644 --- a/RepoZ.Api.Common/Git/RepositoryActions/RepositoryActionConfiguration.cs +++ b/RepoZ.Api.Common/Git/RepositoryActions/RepositoryActionConfiguration.cs @@ -5,6 +5,9 @@ namespace RepoZ.Api.Common.Git { public class RepositoryActionConfiguration { + [JsonProperty("redirect")] + public string RedirectFile { get; set; } + [JsonProperty("repository-actions")] public List RepositoryActions { get; set; } = new List(); @@ -37,6 +40,9 @@ public class RepositoryAction [JsonProperty("active")] public bool Active { get; set; } + + [JsonProperty("subfolder")] + public List Subfolder { get; set; } = new List(); } [System.Diagnostics.DebuggerDisplay("{Name}")] diff --git a/RepoZ.Api.Common/IO/DefaultRepositoryActionProvider.cs b/RepoZ.Api.Common/IO/DefaultRepositoryActionProvider.cs index 95064b7e..9a4df70a 100644 --- a/RepoZ.Api.Common/IO/DefaultRepositoryActionProvider.cs +++ b/RepoZ.Api.Common/IO/DefaultRepositoryActionProvider.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Collections.Generic; using RepoZ.Api.Git; using System.Linq; @@ -64,19 +64,62 @@ private IEnumerable GetContextMenuActionsInternal(IEnumerable< yield return CreateProcessRunnerAction(_translationService.Translate("Fix"), Path.GetDirectoryName(location)); } + // load specific repo config + RepositoryActionConfiguration specificConfig = null; + if (singleRepository != null) + { + var tmpConfig = _repositoryActionConfigurationStore.LoadRepositoryConfiguration(singleRepository); + specificConfig = tmpConfig; + + if (!string.IsNullOrWhiteSpace(tmpConfig?.RedirectFile)) + { + var filename = ReplaceVariables(tmpConfig.RedirectFile, singleRepository); + if (File.Exists(filename)) + { + tmpConfig = _repositoryActionConfigurationStore.LoadRepositoryActionConfiguration(filename); + + if (tmpConfig != null && tmpConfig.State == RepositoryActionConfiguration.LoadState.Ok) + { + specificConfig = tmpConfig; + } + } + } + } + if (singleRepository != null && _configuration.State == RepositoryActionConfiguration.LoadState.Ok) { - foreach (var action in _configuration.RepositoryActions.Where(a => a.Active)) - yield return CreateProcessRunnerAction(action, singleRepository, beginGroup: false); + var repositoryActionConfigurations = new[] {_configuration, specificConfig}; - foreach (var fileAssociaction in _configuration.FileAssociations.Where(a => a.Active)) + foreach (var config in repositoryActionConfigurations) { - yield return CreateFileAssociationSubMenu( - singleRepository, - ReplaceTranslatables(fileAssociaction.Name), - fileAssociaction.Extension); + if (config == null || config.State != RepositoryActionConfiguration.LoadState.Ok) + { + continue; + } + + foreach (var action in config.RepositoryActions.Where(a => a.Active)) + { + yield return CreateProcessRunnerAction(action, singleRepository, beginGroup: false); + } + } + foreach (var config in repositoryActionConfigurations) + { + if (config == null || config.State != RepositoryActionConfiguration.LoadState.Ok) + { + continue; + } + + foreach (var fileAssociation in config.FileAssociations.Where(a => a.Active)) + { + yield return CreateFileAssociationSubMenu( + singleRepository, + ReplaceTranslatables(fileAssociation.Name), + fileAssociation.Extension); + } + } + yield return CreateBrowseRemoteAction(singleRepository); } @@ -90,39 +133,39 @@ private IEnumerable GetContextMenuActionsInternal(IEnumerable< { Name = _translationService.Translate("Checkout"), DeferredSubActionsEnumerator = () => singleRepository.LocalBranches - .Take(50) - .Select(branch => new RepositoryAction() - { - Name = branch, - Action = (_, __) => _repositoryWriter.Checkout(singleRepository, branch), - CanExecute = !singleRepository.CurrentBranch.Equals(branch, StringComparison.OrdinalIgnoreCase) - }) - .Union(new[] - { - new RepositoryAction() - { - BeginGroup = true, - Name = _translationService.Translate("Remote branches"), - DeferredSubActionsEnumerator = () => - { - var remoteBranches = singleRepository.ReadAllBranches().Select(branch => new RepositoryAction() - { - Name = branch, - Action = (_, __) => _repositoryWriter.Checkout(singleRepository, branch), - CanExecute = !singleRepository.CurrentBranch.Equals(branch, StringComparison.OrdinalIgnoreCase) - }).ToArray(); - - if (remoteBranches.Any()) - return remoteBranches; - - return new RepositoryAction[] - { - new RepositoryAction() { Name = _translationService.Translate("No remote branches found"), CanExecute = false }, - new RepositoryAction() { Name = _translationService.Translate("Try to fetch changes if you're expecting remote branches"), CanExecute = false } - }; - } - } }) - .ToArray() + .Take(50) + .Select(branch => new RepositoryAction() + { + Name = branch, + Action = (_, __) => _repositoryWriter.Checkout(singleRepository, branch), + CanExecute = !singleRepository.CurrentBranch.Equals(branch, StringComparison.OrdinalIgnoreCase) + }) + .Union(new[] + { + new RepositoryAction() + { + BeginGroup = true, + Name = _translationService.Translate("Remote branches"), + DeferredSubActionsEnumerator = () => + { + var remoteBranches = singleRepository.ReadAllBranches().Select(branch => new RepositoryAction() + { + Name = branch, + Action = (_, __) => _repositoryWriter.Checkout(singleRepository, branch), + CanExecute = !singleRepository.CurrentBranch.Equals(branch, StringComparison.OrdinalIgnoreCase) + }).ToArray(); + + if (remoteBranches.Any()) + return remoteBranches; + + return new RepositoryAction[] + { + new RepositoryAction() { Name = _translationService.Translate("No remote branches found"), CanExecute = false }, + new RepositoryAction() { Name = _translationService.Translate("Try to fetch changes if you're expecting remote branches"), CanExecute = false } + }; + } + } }) + .ToArray() }; } @@ -136,14 +179,14 @@ private string ReplaceVariables(string value, Repository repository) return Environment.ExpandEnvironmentVariables( value - .Replace("{Repository.Name}", repository.Name) - .Replace("{Repository.Path}", repository.Path) - .Replace("{Repository.SafePath}", repository.SafePath) - .Replace("{Repository.Location}", repository.Location) - .Replace("{Repository.CurrentBranch}", repository.CurrentBranch) - .Replace("{Repository.Branches}", string.Join("|", repository.Branches)) - .Replace("{Repository.LocalBranches}", string.Join("|", repository.LocalBranches)) - .Replace("{Repository.RemoteUrls}", string.Join("|", repository.RemoteUrls))); + .Replace("{Repository.Name}", repository.Name) + .Replace("{Repository.Path}", repository.Path) + .Replace("{Repository.SafePath}", repository.SafePath) + .Replace("{Repository.Location}", repository.Location) + .Replace("{Repository.CurrentBranch}", repository.CurrentBranch) + .Replace("{Repository.Branches}", string.Join("|", repository.Branches)) + .Replace("{Repository.LocalBranches}", string.Join("|", repository.LocalBranches)) + .Replace("{Repository.RemoteUrls}", string.Join("|", repository.RemoteUrls))); } private string ReplaceTranslatables(string value) @@ -176,7 +219,24 @@ private RepositoryAction CreateProcessRunnerAction(RepositoryActionConfiguration var executables = action.Executables.Select(e => ReplaceVariables(e, repository)); var arguments = ReplaceVariables(action.Arguments, repository); - if (string.IsNullOrEmpty(action.Command)) + if (action.Subfolder.Any()) + { + return new RepositoryAction() + { + Name = name, + DeferredSubActionsEnumerator = () => + action.Subfolder + .Select(x => CreateProcessRunnerAction(x, repository, false)) + .ToArray() + }; + } + + if (command.Equals("browser", StringComparison.CurrentCultureIgnoreCase)) + { + // assume arguments is an url. + return CreateProcessRunnerAction(name, arguments); + } + else if (string.IsNullOrEmpty(action.Command)) { foreach (var executable in executables) { @@ -272,10 +332,10 @@ private RepositoryAction CreateFileAssociationSubMenu(Repository repository, str { Name = actionName, DeferredSubActionsEnumerator = () => - GetFiles(repository, filePattern) - .Select(solutionFile => ReplaceVariables(solutionFile, repository)) - .Select(solutionFile => CreateProcessRunnerAction(Path.GetFileName(solutionFile), solutionFile)) - .ToArray() + GetFiles(repository, filePattern) + .Select(solutionFile => ReplaceVariables(solutionFile, repository)) + .Select(solutionFile => CreateProcessRunnerAction(Path.GetFileName(solutionFile), solutionFile)) + .ToArray() }; } @@ -298,9 +358,9 @@ private RepositoryAction CreateBrowseRemoteAction(Repository repository) { Name = actionName, DeferredSubActionsEnumerator = () => repository.RemoteUrls - .Take(50) - .Select(url => CreateProcessRunnerAction(url, url)) - .ToArray() + .Take(50) + .Select(url => CreateProcessRunnerAction(url, url)) + .ToArray() }; } diff --git a/Tests/Git/DefaultRepositoryIgnoreStoreTests.cs b/Tests/Git/DefaultRepositoryIgnoreStoreTests.cs index 733df32b..168e3ac9 100644 --- a/Tests/Git/DefaultRepositoryIgnoreStoreTests.cs +++ b/Tests/Git/DefaultRepositoryIgnoreStoreTests.cs @@ -14,7 +14,9 @@ public class DefaultRepositoryIgnoreStoreTests [OneTimeSetUp] public void Setup() { - _store = new DefaultRepositoryIgnoreStore(new Mock().Object, new Mock().Object); + var appDataPathProvider = new Mock(); + appDataPathProvider.Setup(x => x.GetAppDataPath()).Returns(""); //dummy value + _store = new DefaultRepositoryIgnoreStore(new Mock().Object, appDataPathProvider.Object); _store.UseFilePersistence = false; _store.IgnoreByPath(@"C:\data\repos\first");