diff --git a/LibGit2Sharp.Tests/ConfigurationFixture.cs b/LibGit2Sharp.Tests/ConfigurationFixture.cs index 1799ef74d..30c8b207f 100644 --- a/LibGit2Sharp.Tests/ConfigurationFixture.cs +++ b/LibGit2Sharp.Tests/ConfigurationFixture.cs @@ -431,5 +431,127 @@ public void CannotBuildAProperSignatureFromConfigWhenFullIdentityCannotBeFoundIn Assert.Null(signature); } } + + [Fact] + public void CanSetAndGetSearchPath() + { + string globalPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + string systemPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + string xdgPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, globalPath); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, systemPath); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, xdgPath); + + Assert.Equal(globalPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global).Single()); + Assert.Equal(systemPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.System).Single()); + Assert.Equal(xdgPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Xdg).Single()); + + // reset the search paths to their defaults + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, null); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, null); + } + + [Fact] + public void CanSetAndGetMultipleSearchPaths() + { + string[] paths = + { + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + }; + + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, paths); + + Assert.Equal(paths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + + // set back to the defaults + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } + + [Fact] + public void CanResetSearchPaths() + { + // all of these calls should reset the config path to the default + Action[] resetActions = + { + () => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global), + () => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null), + () => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, string.Empty), + () => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, new string[] { }), + }; + + // record the default search path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + var oldPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + Assert.NotNull(oldPaths); + + // generate a non-default path to set + var newPaths = new string[] { Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()) }; + + foreach (var tryToReset in resetActions) + { + // change to the non-default path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, newPaths); + Assert.Equal(newPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + + // set it back to the default + tryToReset(); + Assert.Equal(oldPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + } + + // make sure the config paths are reset after the test ends + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } + + [Fact] + public void CanAppendToSearchPaths() + { + string appendMe = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + var prevPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + + // append using the special name $PATH + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, "$PATH", appendMe); + + var currentPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + Assert.Equal(currentPaths, prevPaths.Concat(new[] { appendMe })); + + // set it back to the default + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } + + [Fact] + public void CanRedirectConfigAccess() + { + var scd1 = BuildSelfCleaningDirectory(); + var scd2 = BuildSelfCleaningDirectory(); + + Touch(scd1.RootedDirectoryPath, ".gitconfig"); + Touch(scd2.RootedDirectoryPath, ".gitconfig"); + + // redirect global access to the first path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd1.RootedDirectoryPath); + + // set a value in the first config + using (var config = Configuration.BuildFrom(null)) + { + config.Set("luggage.code", 9876, ConfigurationLevel.Global); + Assert.Equal(9876, config.Get("luggage.code", ConfigurationLevel.Global).Value); + } + + // redirect global config access to path2 + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd2.RootedDirectoryPath); + + // if the redirect succeeds, the value set in the prior config should not be visible + using (var config = Configuration.BuildFrom(null)) + { + Assert.Equal(-1, config.GetValueOrDefault("luggage.code", ConfigurationLevel.Global, -1)); + } + + // reset the search path to the default + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index a1b5b5cf1..7648b8f9e 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -581,6 +581,25 @@ internal static extern int git_filter_unregister( [DllImport(libgit2)] internal static extern int git_libgit2_features(); + #region git_libgit2_opts + + // Bindings for git_libgit2_opts(int option, ...): + // Currently only GIT_OPT_GET_SEARCH_PATH and GIT_OPT_SET_SEARCH_PATH are supported, + // but other overloads could be added using a similar pattern. + // CallingConvention.Cdecl is used to allow binding the the C varargs signature, and each possible call signature must be enumerated. + // __argslist was an option, but is an undocumented feature that should likely not be used here. + + // git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, uint level, GitBuf buf); + + // git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, uint level, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))]string path); + + #endregion + [DllImport(libgit2)] internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 11b3ca209..b31e34bdb 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -3193,6 +3193,60 @@ public static BuiltInFeatures git_libgit2_features() return (BuiltInFeatures)NativeMethods.git_libgit2_features(); } + // C# equivalent of libgit2's git_libgit2_opt_t + private enum LibGitOption + { + GetMWindowSize, // GIT_OPT_GET_MWINDOW_SIZE + SetMWindowSize, // GIT_OPT_SET_MWINDOW_SIZE + GetMWindowMappedLimit, // GIT_OPT_GET_MWINDOW_MAPPED_LIMIT + SetMWindowMappedLimit, // GIT_OPT_SET_MWINDOW_MAPPED_LIMIT + GetSearchPath, // GIT_OPT_GET_SEARCH_PATH + SetSearchPath, // GIT_OPT_SET_SEARCH_PATH + SetCacheObjectLimit, // GIT_OPT_SET_CACHE_OBJECT_LIMIT + SetCacheMaxSize, // GIT_OPT_SET_CACHE_MAX_SIZE + EnableCaching, // GIT_OPT_ENABLE_CACHING + GetCachedMemory, // GIT_OPT_GET_CACHED_MEMORY + GetTemplatePath, // GIT_OPT_GET_TEMPLATE_PATH + SetTemplatePath, // GIT_OPT_SET_TEMPLATE_PATH + SetSslCertLocations, // GIT_OPT_SET_SSL_CERT_LOCATIONS + } + + /// + /// Get the paths under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// + /// The paths delimited by 'GIT_PATH_LIST_SEPARATOR' (). + /// + public static string git_libgit2_opts_get_search_path(ConfigurationLevel level) + { + string path; + + using (var buf = new GitBuf()) + { + var res = NativeMethods.git_libgit2_opts((int)LibGitOption.GetSearchPath, (uint)level, buf); + Ensure.ZeroResult(res); + + path = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + + return path; + } + + /// + /// Set the path(s) under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// + /// A string of paths delimited by 'GIT_PATH_LIST_SEPARATOR' (). + /// Pass null to reset the search path to the default. + /// + public static void git_libgit2_opts_set_search_path(ConfigurationLevel level, string path) + { + var res = NativeMethods.git_libgit2_opts((int)LibGitOption.SetSearchPath, (uint)level, path); + Ensure.ZeroResult(res); + } + #endregion private static ICollection git_foreach( diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 399ada7cf..b3ec3cac8 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Collections.Generic; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -272,5 +273,32 @@ internal static void DeregisterFilter(Filter filter) DeregisterFilter(registration); } } + + /// + /// Get the paths under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// The paths that are searched + public static IEnumerable GetConfigSearchPaths(ConfigurationLevel level) + { + return Proxy.git_libgit2_opts_get_search_path(level).Split(Path.PathSeparator); + } + + /// + /// Set the paths under which libgit2 searches for the configuration file of a given level. + /// + /// . + /// + /// The level (global/system/XDG) of the config. + /// + /// The new search paths to set. + /// Pass null to reset to the default. + /// The special string "$PATH" will be substituted with the current search path. + /// + public static void SetConfigSearchPaths(ConfigurationLevel level, params string[] paths) + { + var pathString = (paths == null) ? null : string.Join(Path.PathSeparator.ToString(), paths); + Proxy.git_libgit2_opts_set_search_path(level, pathString); + } } } diff --git a/LibGit2Sharp/RepositoryOptions.cs b/LibGit2Sharp/RepositoryOptions.cs index a8cf0c7f8..a4e9541f8 100644 --- a/LibGit2Sharp/RepositoryOptions.cs +++ b/LibGit2Sharp/RepositoryOptions.cs @@ -32,6 +32,7 @@ public sealed class RepositoryOptions /// The path has either to lead to an existing valid configuration file, /// or to a non existent configuration file which will be eventually created. /// + /// . /// public string GlobalConfigurationLocation { get; set; } @@ -41,6 +42,7 @@ public sealed class RepositoryOptions /// The path has either to lead to an existing valid configuration file, /// or to a non existent configuration file which will be eventually created. /// + /// . /// public string XdgConfigurationLocation { get; set; } @@ -50,6 +52,7 @@ public sealed class RepositoryOptions /// The path has to lead to an existing valid configuration file, /// or to a non existent configuration file which will be eventually created. /// + /// . /// public string SystemConfigurationLocation { get; set; }