Skip to content

Commit f95f747

Browse files
authored
Merge pull request #72 from mjcheetham/multivar-git
New Install 4/6: Extend IGitConfiguration to allow manipulating multivars
2 parents ffa470c + 040bb9e commit f95f747

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)