/// <summary> /// Finds and returns paths to Git installations in common locations. /// </summary> /// <param name="hints">(optional) List of paths the caller believes Git can be found.</param> /// <param name="paths"> /// All discovered paths to the root of Git installations, ordered by 'priority' with first /// being the best installation to use when shelling out to Git.exe. /// </param> /// <returns><see langword="True"/> if Git was detected; <see langword="false"/> otherwise.</returns> public static bool FindGitInstallations(out List <GitInstallation> installations) { const string GitAppName = @"Git"; const string GitSubkeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Git_is1"; const string GitValueName = "InstallLocation"; installations = null; var programFiles32Path = String.Empty; var programFiles64Path = String.Empty; var appDataRoamingPath = String.Empty; var appDataLocalPath = String.Empty; var programDataPath = String.Empty; var reg32HklmPath = String.Empty; var reg64HklmPath = String.Empty; var reg32HkcuPath = String.Empty; var reg64HkcuPath = String.Empty; var shellPathValue = String.Empty; RegistryKey reg32HklmKey = null; RegistryKey reg64HklmKey = null; RegistryKey reg32HkcuKey = null; RegistryKey reg64HkcuKey = null; if ((reg32HklmKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) != null) { if ((reg32HklmKey = reg32HklmKey.OpenSubKey(GitSubkeyName)) != null) { reg32HklmPath = reg32HklmKey.GetValue(GitValueName, reg32HklmPath) as String; } } if ((reg32HkcuKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32)) != null) { if ((reg32HkcuKey = reg32HkcuKey.OpenSubKey(GitSubkeyName)) != null) { reg32HkcuPath = reg32HkcuKey.GetValue(GitValueName, reg32HkcuPath) as String; } } if ((programFiles32Path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)) != null) { programFiles32Path = Path.Combine(programFiles32Path, GitAppName); } if (Environment.Is64BitOperatingSystem) { if ((reg64HklmKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) != null) { if ((reg64HklmKey = reg64HklmKey.OpenSubKey(GitSubkeyName)) != null) { reg64HklmPath = reg64HklmKey.GetValue(GitValueName, reg64HklmPath) as String; } } if ((reg64HkcuKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64)) != null) { if ((reg64HkcuKey = reg64HkcuKey.OpenSubKey(GitSubkeyName)) != null) { reg64HkcuPath = reg64HkcuKey.GetValue(GitValueName, reg64HkcuPath) as String; } } // since .NET returns %ProgramFiles% as %ProgramFilesX86% when the app is 32-bit // manual manipulation of the path is required if ((programFiles64Path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)) != null) { programFiles64Path = programFiles64Path.Substring(0, programFiles64Path.Length - 6); programFiles64Path = Path.Combine(programFiles64Path, GitAppName); } } if ((appDataRoamingPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)) != null) { appDataRoamingPath = Path.Combine(appDataRoamingPath, GitAppName); } if ((appDataLocalPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)) != null) { appDataLocalPath = Path.Combine(appDataLocalPath, GitAppName); } if ((programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)) != null) { programDataPath = Path.Combine(programDataPath, GitAppName); } List <GitInstallation> candidates = new List <GitInstallation>(); // add candidate locations in order of preference if (Where.FindApp(GitAppName, out shellPathValue)) { // `Where.App` returns the path to the executable, truncate to the installation root if (shellPathValue.EndsWith(GitInstallation.AllVersionCmdPath, StringComparison.InvariantCultureIgnoreCase)) { shellPathValue = shellPathValue.Substring(0, shellPathValue.Length - GitInstallation.AllVersionCmdPath.Length); } candidates.Add(new GitInstallation(shellPathValue, KnownGitDistribution.GitForWindows64v2)); candidates.Add(new GitInstallation(shellPathValue, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(shellPathValue, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(reg64HklmPath)) { candidates.Add(new GitInstallation(reg64HklmPath, KnownGitDistribution.GitForWindows64v2)); } if (!String.IsNullOrEmpty(programFiles32Path)) { candidates.Add(new GitInstallation(programFiles64Path, KnownGitDistribution.GitForWindows64v2)); } if (!String.IsNullOrEmpty(reg64HkcuPath)) { candidates.Add(new GitInstallation(reg64HkcuPath, KnownGitDistribution.GitForWindows64v2)); } if (!String.IsNullOrEmpty(reg32HklmPath)) { candidates.Add(new GitInstallation(reg32HklmPath, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(reg32HklmPath, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(programFiles32Path)) { candidates.Add(new GitInstallation(programFiles32Path, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(programFiles32Path, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(reg32HkcuPath)) { candidates.Add(new GitInstallation(reg32HkcuPath, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(reg32HkcuPath, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(programDataPath)) { candidates.Add(new GitInstallation(programDataPath, KnownGitDistribution.GitForWindows64v2)); candidates.Add(new GitInstallation(programDataPath, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(programDataPath, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(appDataLocalPath)) { candidates.Add(new GitInstallation(appDataLocalPath, KnownGitDistribution.GitForWindows64v2)); candidates.Add(new GitInstallation(appDataLocalPath, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(appDataLocalPath, KnownGitDistribution.GitForWindows32v1)); } if (!String.IsNullOrEmpty(appDataRoamingPath)) { candidates.Add(new GitInstallation(appDataRoamingPath, KnownGitDistribution.GitForWindows64v2)); candidates.Add(new GitInstallation(appDataRoamingPath, KnownGitDistribution.GitForWindows32v2)); candidates.Add(new GitInstallation(appDataRoamingPath, KnownGitDistribution.GitForWindows32v1)); } HashSet <GitInstallation> pathSet = new HashSet <GitInstallation>(); foreach (var candidate in candidates) { if (GitInstallation.IsValid(candidate)) { pathSet.Add(candidate); } } installations = pathSet.ToList(); Git.Trace.WriteLine($"found {installations.Count} Git installation(s)."); return(installations.Count > 0); }
internal static void ParseGitConfig(TextReader reader, IDictionary <string, string> destination) { Debug.Assert(reader != null, $"The `{nameof(reader)}` parameter is null."); Debug.Assert(destination != null, $"The `{nameof(destination)}` parameter is null."); Match match = null; string section = null; // parse each line in the config independently - Git's configs do not accept multi-line values string line; while ((line = reader.ReadLine()) != null) { // skip empty and commented lines if (String.IsNullOrWhiteSpace(line)) { continue; } if (CommentRegex.Value.IsMatch(line)) { 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 = SectionRegex.Value.Match(line)).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.Length > 0 && val[0] == '"') { if (val[val.Length - 1] == '"' && 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 = KeyValueRegex.Value.Match(line)).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.Length > 0 && val[0] == '"') { if (val[val.Length - 1] == '"' && val.Length > 1) { val = val.Substring(1, val.Length - 2); } else { val = val.Substring(1, val.Length - 1); } } // Test for and handle include directives if ("include.path".Equals(key)) { try { // This is an include directive, import the configuration values from the included file string includePath = (val.StartsWith("~/", StringComparison.OrdinalIgnoreCase)) ? Where.Home() + val.Substring(1, val.Length - 1) : val; includePath = Path.GetFullPath(includePath); using (var includeFile = File.Open(includePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (var includeReader = new StreamReader(includeFile)) { ParseGitConfig(includeReader, destination); } } catch (Exception exception) { Trace.WriteLine($"failed to parse config file: {val}. {exception.Message}"); } } else { // Add or update the (key, value) if (destination.ContainsKey(key)) { destination[key] = val; } else { destination.Add(key, val); } } } } } }