Skip to content

Commit 8688f10

Browse files
committed
Merge pull request #1123 from rcorre/search-path
Allow libgit2# to redirect the global/system/xdg config search path
2 parents e734df5 + d79eec5 commit 8688f10

File tree

5 files changed

+226
-0
lines changed

5 files changed

+226
-0
lines changed

LibGit2Sharp.Tests/ConfigurationFixture.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,5 +431,127 @@ public void CannotBuildAProperSignatureFromConfigWhenFullIdentityCannotBeFoundIn
431431
Assert.Null(signature);
432432
}
433433
}
434+
435+
[Fact]
436+
public void CanSetAndGetSearchPath()
437+
{
438+
string globalPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName());
439+
string systemPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName());
440+
string xdgPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName());
441+
442+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, globalPath);
443+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, systemPath);
444+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, xdgPath);
445+
446+
Assert.Equal(globalPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global).Single());
447+
Assert.Equal(systemPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.System).Single());
448+
Assert.Equal(xdgPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Xdg).Single());
449+
450+
// reset the search paths to their defaults
451+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
452+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, null);
453+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, null);
454+
}
455+
456+
[Fact]
457+
public void CanSetAndGetMultipleSearchPaths()
458+
{
459+
string[] paths =
460+
{
461+
Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()),
462+
Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()),
463+
Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()),
464+
};
465+
466+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, paths);
467+
468+
Assert.Equal(paths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global));
469+
470+
// set back to the defaults
471+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
472+
}
473+
474+
[Fact]
475+
public void CanResetSearchPaths()
476+
{
477+
// all of these calls should reset the config path to the default
478+
Action[] resetActions =
479+
{
480+
() => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global),
481+
() => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null),
482+
() => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, string.Empty),
483+
() => GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, new string[] { }),
484+
};
485+
486+
// record the default search path
487+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
488+
var oldPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global);
489+
Assert.NotNull(oldPaths);
490+
491+
// generate a non-default path to set
492+
var newPaths = new string[] { Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()) };
493+
494+
foreach (var tryToReset in resetActions)
495+
{
496+
// change to the non-default path
497+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, newPaths);
498+
Assert.Equal(newPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global));
499+
500+
// set it back to the default
501+
tryToReset();
502+
Assert.Equal(oldPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global));
503+
}
504+
505+
// make sure the config paths are reset after the test ends
506+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
507+
}
508+
509+
[Fact]
510+
public void CanAppendToSearchPaths()
511+
{
512+
string appendMe = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName());
513+
var prevPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global);
514+
515+
// append using the special name $PATH
516+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, "$PATH", appendMe);
517+
518+
var currentPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global);
519+
Assert.Equal(currentPaths, prevPaths.Concat(new[] { appendMe }));
520+
521+
// set it back to the default
522+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
523+
}
524+
525+
[Fact]
526+
public void CanRedirectConfigAccess()
527+
{
528+
var scd1 = BuildSelfCleaningDirectory();
529+
var scd2 = BuildSelfCleaningDirectory();
530+
531+
Touch(scd1.RootedDirectoryPath, ".gitconfig");
532+
Touch(scd2.RootedDirectoryPath, ".gitconfig");
533+
534+
// redirect global access to the first path
535+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd1.RootedDirectoryPath);
536+
537+
// set a value in the first config
538+
using (var config = Configuration.BuildFrom(null))
539+
{
540+
config.Set("luggage.code", 9876, ConfigurationLevel.Global);
541+
Assert.Equal(9876, config.Get<int>("luggage.code", ConfigurationLevel.Global).Value);
542+
}
543+
544+
// redirect global config access to path2
545+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd2.RootedDirectoryPath);
546+
547+
// if the redirect succeeds, the value set in the prior config should not be visible
548+
using (var config = Configuration.BuildFrom(null))
549+
{
550+
Assert.Equal(-1, config.GetValueOrDefault<int>("luggage.code", ConfigurationLevel.Global, -1));
551+
}
552+
553+
// reset the search path to the default
554+
GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null);
555+
}
434556
}
435557
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,25 @@ internal static extern int git_filter_unregister(
581581
[DllImport(libgit2)]
582582
internal static extern int git_libgit2_features();
583583

584+
#region git_libgit2_opts
585+
586+
// Bindings for git_libgit2_opts(int option, ...):
587+
// Currently only GIT_OPT_GET_SEARCH_PATH and GIT_OPT_SET_SEARCH_PATH are supported,
588+
// but other overloads could be added using a similar pattern.
589+
// CallingConvention.Cdecl is used to allow binding the the C varargs signature, and each possible call signature must be enumerated.
590+
// __argslist was an option, but is an undocumented feature that should likely not be used here.
591+
592+
// git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf)
593+
[DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)]
594+
internal static extern int git_libgit2_opts(int option, uint level, GitBuf buf);
595+
596+
// git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path)
597+
[DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)]
598+
internal static extern int git_libgit2_opts(int option, uint level,
599+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))]string path);
600+
601+
#endregion
602+
584603
[DllImport(libgit2)]
585604
internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two);
586605

LibGit2Sharp/Core/Proxy.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3193,6 +3193,60 @@ public static BuiltInFeatures git_libgit2_features()
31933193
return (BuiltInFeatures)NativeMethods.git_libgit2_features();
31943194
}
31953195

3196+
// C# equivalent of libgit2's git_libgit2_opt_t
3197+
private enum LibGitOption
3198+
{
3199+
GetMWindowSize, // GIT_OPT_GET_MWINDOW_SIZE
3200+
SetMWindowSize, // GIT_OPT_SET_MWINDOW_SIZE
3201+
GetMWindowMappedLimit, // GIT_OPT_GET_MWINDOW_MAPPED_LIMIT
3202+
SetMWindowMappedLimit, // GIT_OPT_SET_MWINDOW_MAPPED_LIMIT
3203+
GetSearchPath, // GIT_OPT_GET_SEARCH_PATH
3204+
SetSearchPath, // GIT_OPT_SET_SEARCH_PATH
3205+
SetCacheObjectLimit, // GIT_OPT_SET_CACHE_OBJECT_LIMIT
3206+
SetCacheMaxSize, // GIT_OPT_SET_CACHE_MAX_SIZE
3207+
EnableCaching, // GIT_OPT_ENABLE_CACHING
3208+
GetCachedMemory, // GIT_OPT_GET_CACHED_MEMORY
3209+
GetTemplatePath, // GIT_OPT_GET_TEMPLATE_PATH
3210+
SetTemplatePath, // GIT_OPT_SET_TEMPLATE_PATH
3211+
SetSslCertLocations, // GIT_OPT_SET_SSL_CERT_LOCATIONS
3212+
}
3213+
3214+
/// <summary>
3215+
/// Get the paths under which libgit2 searches for the configuration file of a given level.
3216+
/// </summary>
3217+
/// <param name="level">The level (global/system/XDG) of the config.</param>
3218+
/// <returns>
3219+
/// The paths delimited by 'GIT_PATH_LIST_SEPARATOR' (<see cref="GlobalSettings.PathListSeparator"/>).
3220+
/// </returns>
3221+
public static string git_libgit2_opts_get_search_path(ConfigurationLevel level)
3222+
{
3223+
string path;
3224+
3225+
using (var buf = new GitBuf())
3226+
{
3227+
var res = NativeMethods.git_libgit2_opts((int)LibGitOption.GetSearchPath, (uint)level, buf);
3228+
Ensure.ZeroResult(res);
3229+
3230+
path = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty;
3231+
}
3232+
3233+
return path;
3234+
}
3235+
3236+
/// <summary>
3237+
/// Set the path(s) under which libgit2 searches for the configuration file of a given level.
3238+
/// </summary>
3239+
/// <param name="level">The level (global/system/XDG) of the config.</param>
3240+
/// <param name="path">
3241+
/// A string of paths delimited by 'GIT_PATH_LIST_SEPARATOR' (<see cref="GlobalSettings.PathListSeparator"/>).
3242+
/// Pass null to reset the search path to the default.
3243+
/// </param>
3244+
public static void git_libgit2_opts_set_search_path(ConfigurationLevel level, string path)
3245+
{
3246+
var res = NativeMethods.git_libgit2_opts((int)LibGitOption.SetSearchPath, (uint)level, path);
3247+
Ensure.ZeroResult(res);
3248+
}
3249+
31963250
#endregion
31973251

31983252
private static ICollection<TResult> git_foreach<T, TResult>(

LibGit2Sharp/GlobalSettings.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Reflection;
5+
using System.Collections.Generic;
56
using LibGit2Sharp.Core;
67

78
namespace LibGit2Sharp
@@ -272,5 +273,32 @@ internal static void DeregisterFilter(Filter filter)
272273
DeregisterFilter(registration);
273274
}
274275
}
276+
277+
/// <summary>
278+
/// Get the paths under which libgit2 searches for the configuration file of a given level.
279+
/// </summary>
280+
/// <param name="level">The level (global/system/XDG) of the config.</param>
281+
/// <returns>The paths that are searched</returns>
282+
public static IEnumerable<string> GetConfigSearchPaths(ConfigurationLevel level)
283+
{
284+
return Proxy.git_libgit2_opts_get_search_path(level).Split(Path.PathSeparator);
285+
}
286+
287+
/// <summary>
288+
/// Set the paths under which libgit2 searches for the configuration file of a given level.
289+
///
290+
/// <seealso cref="RepositoryOptions"/>.
291+
/// </summary>
292+
/// <param name="level">The level (global/system/XDG) of the config.</param>
293+
/// <param name="paths">
294+
/// The new search paths to set.
295+
/// Pass null to reset to the default.
296+
/// The special string "$PATH" will be substituted with the current search path.
297+
/// </param>
298+
public static void SetConfigSearchPaths(ConfigurationLevel level, params string[] paths)
299+
{
300+
var pathString = (paths == null) ? null : string.Join(Path.PathSeparator.ToString(), paths);
301+
Proxy.git_libgit2_opts_set_search_path(level, pathString);
302+
}
275303
}
276304
}

LibGit2Sharp/RepositoryOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public sealed class RepositoryOptions
3232
/// The path has either to lead to an existing valid configuration file,
3333
/// or to a non existent configuration file which will be eventually created.
3434
/// </para>
35+
/// <seealso cref="GlobalSettings.SetConfigSearchPaths"/>.
3536
/// </summary>
3637
public string GlobalConfigurationLocation { get; set; }
3738

@@ -41,6 +42,7 @@ public sealed class RepositoryOptions
4142
/// The path has either to lead to an existing valid configuration file,
4243
/// or to a non existent configuration file which will be eventually created.
4344
/// </para>
45+
/// <seealso cref="GlobalSettings.SetConfigSearchPaths"/>.
4446
/// </summary>
4547
public string XdgConfigurationLocation { get; set; }
4648

@@ -50,6 +52,7 @@ public sealed class RepositoryOptions
5052
/// The path has to lead to an existing valid configuration file,
5153
/// or to a non existent configuration file which will be eventually created.
5254
/// </para>
55+
/// <seealso cref="GlobalSettings.SetConfigSearchPaths"/>.
5356
/// </summary>
5457
public string SystemConfigurationLocation { get; set; }
5558

0 commit comments

Comments
 (0)