Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
Moved Git config parsing into the library.
Browse files Browse the repository at this point in the history
  • Loading branch information
J Wyman committed Aug 6, 2015
1 parent c495d5b commit 302f2e0
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 201 deletions.
207 changes: 6 additions & 201 deletions Cli-CredentialHelper/Program.cs
Expand Up @@ -3,13 +3,8 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Authentication;
// couple of local re-delcaration to make code easier to read
using ConfigEntry = System.Collections.Generic.KeyValuePair<string, string>;
using Configuration = System.Collections.Generic.Dictionary<string, string>;

namespace Microsoft.TeamFoundation.CredentialHelper
{
Expand All @@ -18,7 +13,6 @@ class Program
private const string ConfigPrefix = "credential";
private const string SecretsNamespace = "git";
private static readonly VsoTokenScope CredentialScope = VsoTokenScope.CodeWrite;
private const char HostSplitCharacter = '.';

static void Main(string[] args)
{
Expand Down Expand Up @@ -330,10 +324,10 @@ private static void LoadOperationArguments(OperationArguments operationArguments

Trace.WriteLine("Program::LoadOperationArguments");

Configuration config = LoadGitConfiguation();
ConfigEntry entry;
Configuration config = new Configuration();
Configuration.Entry entry;

if (GetGitConfigEntry(config, operationArguments, "authority", out entry))
if (config.TryGetEntry(ConfigPrefix, operationArguments.TargetUri, "authority", out entry))
{
Trace.WriteLine(" authority = " + entry.Value);

Expand Down Expand Up @@ -365,7 +359,7 @@ private static void LoadOperationArguments(OperationArguments operationArguments
}
}

if (GetGitConfigEntry(config, operationArguments, "interactive", out entry))
if (config.TryGetEntry(ConfigPrefix, operationArguments.TargetUri, "interactive", out entry))
{
Trace.WriteLine(" interactive = " + entry.Value);

Expand All @@ -382,7 +376,7 @@ private static void LoadOperationArguments(OperationArguments operationArguments
}
}

if (GetGitConfigEntry(config, operationArguments, "validate", out entry))
if (config.TryGetEntry(ConfigPrefix, operationArguments.TargetUri, "validate", out entry))
{
Trace.WriteLine(" validate = " + entry.Value);

Expand All @@ -393,7 +387,7 @@ private static void LoadOperationArguments(OperationArguments operationArguments
}
}

if (GetGitConfigEntry(config, operationArguments, "writelog", out entry))
if (config.TryGetEntry(ConfigPrefix, operationArguments.TargetUri, "writelog", out entry))
{
Trace.WriteLine(" writelog = " + entry.Value);

Expand All @@ -405,195 +399,6 @@ private static void LoadOperationArguments(OperationArguments operationArguments
}
}

private static Configuration LoadGitConfiguation()
{
string systemConfig = null;
string globalConfig = null;
string localConfig = null;

Trace.WriteLine("Program::LoadGitConfiguation");

// read Git's three configs from lowest priority to highest, overwriting values as
// higher prirority configurations are parsed, storing them in a handy lookup table
Configuration values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

// find and parse Git's system config
if (Where.GitSystemConfig(out systemConfig))
{
ParseGitConfig(systemConfig, values);
}

// find and parse Git's global config
if (Where.GitGlobalConfig(out globalConfig))
{
ParseGitConfig(globalConfig, values);
}

// find and parse Git's local config
if (Where.GitLocalConfig(out localConfig))
{
ParseGitConfig(localConfig, values);
}

foreach (var pair in values)
{
Trace.WriteLine(String.Format(" {0} = {1}", pair.Key, pair.Value));
}

return values;
}

private static void ParseGitConfig(string configPath, Configuration values)
{
Debug.Assert(!String.IsNullOrWhiteSpace(configPath), "The configPath parameter is null or invalid.");
Debug.Assert(File.Exists(configPath), "The configPath parameter references a non-existant file.");
Debug.Assert(values != null, "The configPath parameter is null or invalid.");

Trace.WriteLine("Program::ParseGitConfig");

if (!File.Exists(configPath))
return;

Match match = null;
string section = null;

// parse each line in the config independently - Git's configs do not accept multi-line values
foreach (var line in File.ReadLines(configPath))
{
// skip empty and commented lines
if (String.IsNullOrWhiteSpace(line))
continue;
if (Regex.IsMatch(line, @"^\s*[#;]", RegexOptions.Compiled | RegexOptions.CultureInvariant))
continue;

// sections begin with values like [section] or [section "section name"]. All subsequent lines,
// until a new section is encountered, are children of the section
if ((match = Regex.Match(line, @"^\s*\[\s*(\w+)\s*(\""[^\""]+\""){0,1}\]", RegexOptions.Compiled | RegexOptions.CultureInvariant)).Success)
{
if (match.Groups.Count >= 2 && !String.IsNullOrWhiteSpace(match.Groups[1].Value))
{
section = match.Groups[1].Value.Trim();

// check if the section is named, if so: process the name
if (match.Groups.Count >= 3 && !String.IsNullOrWhiteSpace(match.Groups[2].Value))
{
string val = match.Groups[2].Value.Trim();

// triming off enclosing quotes makes usage easier, only trim in pairs
if (val[0] == '"')
{
if (val[val.Length - 1] == '"')
{
val = val.Substring(1, val.Length - 2);
}
else
{
val = val.Substring(1, val.Length - 1);
}
}

section += HostSplitCharacter + val;
}
}
}
// section children should be in the format of name = value pairs
else if ((match = Regex.Match(line, @"^\s*(\w+)\s*=\s*(.+)", RegexOptions.Compiled | RegexOptions.CultureInvariant)).Success)
{
if (match.Groups.Count >= 3
&& !String.IsNullOrEmpty(match.Groups[1].Value)
&& !String.IsNullOrEmpty(match.Groups[2].Value))
{
string key = section + HostSplitCharacter + match.Groups[1].Value.Trim();
string val = match.Groups[2].Value.Trim();

// triming off enclosing quotes makes usage easier, only trim in pairs
if (val[0] == '"')
{
if (val[val.Length - 1] == '"')
{
val = val.Substring(1, val.Length - 2);
}
else
{
val = val.Substring(1, val.Length - 1);
}
}

// add or update the (key, value)
if (values.ContainsKey(key))
{
values[key] = val;
}
else
{
values.Add(key, val);
}
}
}
}
}

private static bool GetGitConfigEntry(Configuration config, OperationArguments operationArguments, string key, out ConfigEntry entry)
{
Debug.Assert(config != null, "The config parameter is null");
Debug.Assert(operationArguments != null, "The operationArguments parameter is null");
Debug.Assert(operationArguments.Protocol != null, "The operationArguments.Protocol parameter is null");
Debug.Assert(operationArguments.Host != null, "The operationArguments.Host parameter is null");
Debug.Assert(key != null, "The key parameter is null");

Trace.WriteLine("Program::GetGitConfigEntry");

// return match seeking from most specific (credential.<schema>://<uri>.<key>) to least specific (credential.<key>)
if (GetGitConfigEntry(config, ConfigPrefix, String.Format("{0}://{1}", operationArguments.Protocol, operationArguments.Host), key, out entry)
|| GetGitConfigEntry(config, ConfigPrefix, operationArguments.Host, key, out entry))
return true;

if (!String.IsNullOrWhiteSpace(operationArguments.Host))
{
string[] fragments = operationArguments.Host.Split(HostSplitCharacter);
string host = null;

// look for host matches stripping a single sub-domain at a time off
// don't match against a top-level domain (aka ".com")
for (int i = 1; i < fragments.Length - 1; i++)
{
host = String.Join(".", fragments, i, fragments.Length - i);
if (GetGitConfigEntry(config, ConfigPrefix, host, key, out entry))
return true;
}
}

// try to find an unadorned match as a complete fallback
if (GetGitConfigEntry(config, ConfigPrefix, String.Empty, key, out entry))
return true;

// nothing found
entry = default(ConfigEntry);
return false;
}

private static bool GetGitConfigEntry(Configuration config, string prefix, string key, string suffix, out ConfigEntry entry)
{
Debug.Assert(config != null, "The config parameter is null");
Debug.Assert(prefix != null, "The prefix parameter is null");
Debug.Assert(suffix != null, "The suffic parameter is null");

string match = String.IsNullOrEmpty(key)
? String.Format("{0}.{1}", prefix, suffix)
: String.Format("{0}.{1}.{2}", prefix, key, suffix);

// if there's a match, return it
if (config.ContainsKey(match))
{
entry = new ConfigEntry(match, config[match]);
return true;
}

// nothing found
entry = default(ConfigEntry);
return false;
}

private static void LogEvent(string message, EventLogEntryType eventType)
{
//const string EventSource = "TFS Git Credential Helper";
Expand Down

0 comments on commit 302f2e0

Please sign in to comment.