Skip to content

Commit 040bb9e

Browse files
committed
Extend IGitConfiguration to allow manipulating multivars
Extend the IGitConfiguration to allow manipulation of Git configuration 'multivars' (config entries that can have multiple values). This will be used in future commits to set the credential.helper variable to have two values.
1 parent ffa470c commit 040bb9e

File tree

6 files changed

+388
-37
lines changed

6 files changed

+388
-37
lines changed

src/shared/Microsoft.Git.CredentialManager/IGit.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,57 @@ public interface IGitConfiguration : IDisposable
1313
/// <param name="cb">Callback to invoke for each configuration entry.</param>
1414
void Enumerate(GitConfigurationEnumerationCallback cb);
1515

16+
/// <summary>
17+
/// Get a snapshot of the configuration filtered to the specified level.
18+
/// </summary>
19+
/// <param name="level">Configuration level filter.</param>
20+
/// <returns>Git configuration snapshot.</returns>
21+
IGitConfiguration GetFilteredConfiguration(GitConfigurationLevel level);
22+
1623
/// <summary>
1724
/// Try and get the value of a configuration entry as a string.
1825
/// </summary>
1926
/// <param name="name">Configuration entry name.</param>
2027
/// <param name="value">Configuration entry value.</param>
2128
/// <returns>True if the value was found, false otherwise.</returns>
2229
bool TryGetValue(string name, out string value);
30+
31+
/// <summary>
32+
/// Set the value of a configuration entry.
33+
/// </summary>
34+
/// <param name="name">Configuration entry name.</param>
35+
/// <param name="value">Configuration entry value.</param>
36+
void SetValue(string name, string value);
37+
38+
/// <summary>
39+
/// Deletes a configuration entry from the highest level.
40+
/// </summary>
41+
/// <param name="name">Configuration entry name.</param>
42+
void DeleteEntry(string name);
43+
44+
/// <summary>
45+
/// Get all values of a multivar configuration entry.
46+
/// </summary>
47+
/// <param name="name">Configuration entry name.</param>
48+
/// <param name="regexp">Regular expression to filter which variables we're interested in. Use null to indicate all.</param>
49+
/// <returns>All values of the multivar configuration entry.</returns>
50+
IEnumerable<string> GetMultivarValue(string name, string regexp);
51+
52+
/// <summary>
53+
/// Set a multivar configuration entry value.
54+
/// </summary>
55+
/// <param name="name">Configuration entry name.</param>
56+
/// <param name="regexp">Regular expression to indicate which values to replace.</param>
57+
/// <param name="value">Configuration entry value.</param>
58+
/// <remarks>If the regular expression does not match any existing entry, a new entry is created.</remarks>
59+
void SetMultivarValue(string name, string regexp, string value);
60+
61+
/// <summary>
62+
/// Deletes one or several entries from a multivar.
63+
/// </summary>
64+
/// <param name="name">Configuration entry name.</param>
65+
/// <param name="regexp">Regular expression to indicate which values to delete.</param>
66+
void DeleteMultivarEntry(string name, string regexp);
2367
}
2468

2569
/// <summary>
@@ -47,6 +91,15 @@ public interface IGit : IDisposable
4791
string GetRepositoryPath(string path);
4892
}
4993

94+
public enum GitConfigurationLevel
95+
{
96+
ProgramData,
97+
System,
98+
Xdg,
99+
Global,
100+
Local,
101+
}
102+
50103
public static class GitExtensions
51104
{
52105
/// <summary>

src/shared/Microsoft.Git.CredentialManager/Interop/LibGit2.cs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33
using System;
4+
using System.Collections.Generic;
45
using System.IO;
56
using Microsoft.Git.CredentialManager.Interop.Native;
67
using static Microsoft.Git.CredentialManager.Interop.Native.git_config_level_t;
@@ -63,9 +64,8 @@ public string GetRepositoryPath(string path)
6364
{
6465
ThrowIfDisposed();
6566

66-
_trace.WriteLine($"Discovering repository from path '{path}'...");
67-
6867
var buf = new git_buf();
68+
_trace.WriteLine($"Discovering repository from path '{path}'...");
6969
int error = git_repository_discover(buf, path, true, null);
7070

7171
try
@@ -91,6 +91,7 @@ public string GetRepositoryPath(string path)
9191

9292
protected override void ReleaseUnmanagedResources()
9393
{
94+
_trace.WriteLine("Shutting-down libgit2...");
9495
git_libgit2_shutdown();
9596
base.ReleaseUnmanagedResources();
9697
}
@@ -114,6 +115,8 @@ internal unsafe LibGit2Configuration(ITrace trace, git_config* config)
114115
_snapshot = snapshot;
115116
}
116117

118+
#region IGitConfiguration
119+
117120
public unsafe void Enumerate(GitConfigurationEnumerationCallback cb)
118121
{
119122
ThrowIfDisposed();
@@ -143,6 +146,47 @@ int native_cb(git_config_entry entry, void* payload)
143146
}
144147
}
145148

149+
public unsafe IGitConfiguration GetFilteredConfiguration(GitConfigurationLevel level)
150+
{
151+
git_config* filteredConfig;
152+
153+
_trace.WriteLine($"Filtering default configuration set to '{level.ToString()}' level...");
154+
155+
// Filter to the requested level
156+
switch (level)
157+
{
158+
case GitConfigurationLevel.ProgramData:
159+
ThrowIfError(git_config_open_level(&filteredConfig, _config, GIT_CONFIG_LEVEL_PROGRAMDATA),
160+
nameof(git_config_open_default));
161+
break;
162+
163+
case GitConfigurationLevel.System:
164+
ThrowIfError(git_config_open_level(&filteredConfig, _config, GIT_CONFIG_LEVEL_SYSTEM),
165+
nameof(git_config_open_default));
166+
break;
167+
168+
case GitConfigurationLevel.Xdg:
169+
ThrowIfError(git_config_open_level(&filteredConfig, _config, GIT_CONFIG_LEVEL_XDG),
170+
nameof(git_config_open_default));
171+
break;
172+
173+
case GitConfigurationLevel.Global:
174+
ThrowIfError(git_config_open_level(&filteredConfig, _config, GIT_CONFIG_LEVEL_GLOBAL),
175+
nameof(git_config_open_default));
176+
break;
177+
178+
case GitConfigurationLevel.Local:
179+
ThrowIfError(git_config_open_level(&filteredConfig, _config, GIT_CONFIG_LEVEL_LOCAL),
180+
nameof(git_config_open_default));
181+
break;
182+
183+
default:
184+
throw new ArgumentOutOfRangeException(nameof(level), level, null);
185+
}
186+
187+
return new LibGit2Configuration(_trace, filteredConfig);
188+
}
189+
146190
public unsafe bool TryGetValue(string name, out string value)
147191
{
148192
ThrowIfDisposed();
@@ -167,6 +211,84 @@ public unsafe bool TryGetValue(string name, out string value)
167211
return false;
168212
}
169213

214+
public unsafe void SetValue(string name, string value)
215+
{
216+
_trace.WriteLine($"Setting Git configuration entry '{name}' to '{value}'...");
217+
ThrowIfError(git_config_set_string(_config, name, value), nameof(git_config_set_string));
218+
}
219+
220+
public unsafe void DeleteEntry(string name)
221+
{
222+
_trace.WriteLine($"Deleting Git configuration entry '{name}'...");
223+
224+
int result = git_config_delete_entry(_config, name);
225+
switch (result)
226+
{
227+
case GIT_ENOTFOUND:
228+
// Do nothing if asked to delete non-existent key
229+
break;
230+
231+
default:
232+
ThrowIfError(result, nameof(git_config_delete_entry));
233+
break;
234+
}
235+
}
236+
237+
public unsafe IEnumerable<string> GetMultivarValue(string name, string regexp)
238+
{
239+
_trace.WriteLine($"Reading Git configuration multivar '{name}' (regexp: '{regexp}')...");
240+
241+
var values = new List<string>();
242+
243+
int value_callback(git_config_entry entry, void* payload)
244+
{
245+
string value = entry.GetValue();
246+
_trace.WriteLine($"Found multivar value '{value}'.");
247+
values.Add(value);
248+
return 0;
249+
}
250+
251+
int result = git_config_get_multivar_foreach(_config, name, regexp, value_callback, (void*) IntPtr.Zero);
252+
switch (result)
253+
{
254+
case GIT_ENOTFOUND:
255+
// Do nothing if asked to enumerate non-existent multivar key
256+
_trace.WriteLine("No entry found.");
257+
break;
258+
259+
default:
260+
ThrowIfError(result, nameof(git_config_get_multivar_foreach));
261+
break;
262+
}
263+
264+
return values;
265+
}
266+
267+
public unsafe void SetMultivarValue(string name, string regexp, string value)
268+
{
269+
_trace.WriteLine($"Setting Git configuration multivar '{name}' (regexp: '{regexp}') to '{value}'...");
270+
ThrowIfError(git_config_set_multivar(_config, name, regexp, value), nameof(git_config_set_multivar));
271+
}
272+
273+
public unsafe void DeleteMultivarEntry(string name, string regexp)
274+
{
275+
_trace.WriteLine($"Deleting Git configuration multivar '{name}' (regexp: '{regexp}')...");
276+
277+
int result = git_config_delete_multivar(_config, name, regexp);
278+
switch (result)
279+
{
280+
case GIT_ENOTFOUND:
281+
// Do nothing if asked to delete non-existent key
282+
break;
283+
284+
default:
285+
ThrowIfError(result, nameof(git_config_delete_multivar));
286+
break;
287+
}
288+
}
289+
290+
#endregion
291+
170292
protected override void ReleaseUnmanagedResources()
171293
{
172294
unsafe

src/shared/Microsoft.Git.CredentialManager/Interop/Native/LibGit2.Error.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,20 @@ public static void ThrowIfError(int result, string functionName = null)
164164
{
165165
unsafe
166166
{
167-
git_error error = git_error_last();
168-
169-
string errorMessage = U8StringConverter.ToManaged(error.message);
170-
171167
string mainMessage = functionName is null
172168
? $"libgit2 '{functionName}' returned non-zero value"
173169
: "libgit2 returned non-zero value";
174170

175-
throw new InteropException(mainMessage, result, new Exception(errorMessage));
171+
git_error* error = git_error_last();
172+
173+
if (error != null && error->message != null)
174+
{
175+
string errorMessage = U8StringConverter.ToManaged(error->message);
176+
177+
throw new InteropException(mainMessage, result, new Exception(errorMessage));
178+
}
179+
180+
throw new InteropException(mainMessage, result);
176181
}
177182
}
178183
}

src/shared/Microsoft.Git.CredentialManager/Interop/Native/LibGit2.cs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static partial class LibGit2
3838
public static extern int git_libgit2_shutdown();
3939

4040
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
41-
public static extern git_error git_error_last();
41+
public static extern unsafe git_error* git_error_last();
4242

4343
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
4444
public static extern void git_buf_dispose(git_buf buf);
@@ -75,9 +75,55 @@ public static extern unsafe int git_config_get_string(
7575
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
7676
public static extern unsafe int git_config_foreach(git_config* cfg, git_config_foreach_cb callback, void* payload);
7777

78+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
79+
public static extern unsafe int git_config_set_string(
80+
git_config* cfg,
81+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
82+
string name,
83+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
84+
string value);
85+
86+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
87+
public static extern unsafe int git_config_delete_entry(
88+
git_config* cfg,
89+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
90+
string name);
91+
92+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
93+
public static extern unsafe int git_config_get_multivar_foreach(
94+
git_config* cfg,
95+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
96+
string name,
97+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
98+
string regexp,
99+
git_config_foreach_cb callback,
100+
void *payload);
101+
102+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
103+
public static extern unsafe int git_config_set_multivar(
104+
git_config* cfg,
105+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
106+
string name,
107+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
108+
string regexp,
109+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
110+
string value);
111+
112+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
113+
public static extern unsafe int git_config_delete_multivar(
114+
git_config* cfg,
115+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
116+
string name,
117+
[MarshalAs(CustomMarshaler, MarshalCookie = NativeCookie, MarshalTypeRef = typeof(U8StringMarshaler))]
118+
string regexp);
119+
120+
78121
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
79122
public static extern unsafe int git_config_open_default(git_config** @out);
80123

124+
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
125+
public static extern unsafe int git_config_open_level(git_config** @out, git_config* parent, git_config_level_t level);
126+
81127
[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
82128
public static extern unsafe int git_config_snapshot(git_config** @out, git_config* config);
83129
}
@@ -86,9 +132,9 @@ public static extern unsafe int git_config_get_string(
86132
public delegate void git_config_entry_free_callback(git_config_entry entry);
87133

88134
[StructLayout(LayoutKind.Sequential)]
89-
public class git_error
135+
public unsafe struct git_error
90136
{
91-
public unsafe byte* message;
137+
public byte* message;
92138
public int klass;
93139
}
94140

0 commit comments

Comments
 (0)