public IEnumerable <AzureReposBinding> GetBindings(string orgName = null)
        {
            var globalUsers = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            var localUsers  = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            IGitConfiguration config = _git.GetConfiguration();

            string orgPrefix = $"{AzureDevOpsConstants.UrnOrgPrefix}/";

            bool ExtractUserBinding(GitConfigurationEntry entry, IDictionary <string, string> dict)
            {
                if (GitConfigurationKeyComparer.TrySplit(entry.Key, out _, out string scope, out _) &&
                    Uri.TryCreate(scope, UriKind.Absolute, out Uri uri) &&
                    uri.Scheme == AzureDevOpsConstants.UrnScheme && uri.AbsolutePath.StartsWith(orgPrefix))
                {
                    string entryOrgName = uri.AbsolutePath.Substring(orgPrefix.Length);
                    if (string.IsNullOrWhiteSpace(orgName) || StringComparer.OrdinalIgnoreCase.Equals(entryOrgName, orgName))
                    {
                        dict[entryOrgName] = entry.Value;
                    }
                }

                return(true);
            }

            // Only enumerate local configuration if we are inside a repository
            if (_git.IsInsideRepository())
            {
                config.Enumerate(
                    GitConfigurationLevel.Local,
                    Constants.GitConfiguration.Credential.SectionName,
                    Constants.GitConfiguration.Credential.UserName,
                    entry => ExtractUserBinding(entry, localUsers));
            }

            config.Enumerate(
                GitConfigurationLevel.Global,
                Constants.GitConfiguration.Credential.SectionName,
                Constants.GitConfiguration.Credential.UserName,
                entry => ExtractUserBinding(entry, globalUsers));

            foreach (string org in globalUsers.Keys.Union(localUsers.Keys))
            {
                // NOT using the short-circuiting OR operator here on purpose - we need both branches to be evaluated
                if (globalUsers.TryGetValue(org, out string globalUser) | localUsers.TryGetValue(org, out string localUser))
                {
                    yield return(new AzureReposBinding(org, globalUser, localUser));
                }
            }
        }
        public IEnumerable <AzureReposBinding> GetBindings(string orgName = null)
        {
            var globalUsers = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            var localUsers  = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            IGitConfiguration config = _git.GetConfiguration();

            string orgPrefix = $"{AzureDevOpsConstants.UrnOrgPrefix}/";

            config.Enumerate(
                Constants.GitConfiguration.Credential.SectionName,
                Constants.GitConfiguration.Credential.UserName,
                entry =>
            {
                if (GitConfigurationKeyComparer.TrySplit(entry.Key, out _, out string scope, out _) &&
                    Uri.TryCreate(scope, UriKind.Absolute, out Uri uri) &&
                    uri.Scheme == AzureDevOpsConstants.UrnScheme && uri.AbsolutePath.StartsWith(orgPrefix))
                {
                    string entryOrgName = uri.AbsolutePath.Substring(orgPrefix.Length);
                    if (orgName is null || StringComparer.OrdinalIgnoreCase.Equals(entryOrgName, orgName))
                    {
                        if (entry.Level == GitConfigurationLevel.Local)
                        {
                            localUsers[entryOrgName] = entry.Value;
                        }
                        else
                        {
                            globalUsers[entryOrgName] = entry.Value;
                        }
                    }
                }

                return(true);
            });
예제 #3
0
        public void GitConfiguration_Enumerate_CallbackReturnsTrue_InvokesCallbackForEachEntry()
        {
            string repoPath = CreateRepository(out string workDirPath);

            ExecGit(repoPath, workDirPath, "config --local foo.name lancelot").AssertSuccess();
            ExecGit(repoPath, workDirPath, "config --local foo.quest seek-holy-grail").AssertSuccess();
            ExecGit(repoPath, workDirPath, "config --local foo.favcolor blue").AssertSuccess();

            var expectedVisitedEntries = new List <(string name, string value)>
            {
                ("foo.name", "lancelot"),
                ("foo.quest", "seek-holy-grail"),
                ("foo.favcolor", "blue")
            };

            string            gitPath = GetGitPath();
            var               trace   = new NullTrace();
            var               env     = new TestEnvironment();
            var               git     = new GitProcess(trace, env, gitPath, repoPath);
            IGitConfiguration config  = git.GetConfiguration();

            var actualVisitedEntries = new List <(string name, string value)>();

            bool cb(GitConfigurationEntry entry)
            {
                if (entry.Key.StartsWith("foo."))
                {
                    actualVisitedEntries.Add((entry.Key, entry.Value));
                }

                // Continue enumeration
                return(true);
            }

            config.Enumerate(cb);

            Assert.Equal(expectedVisitedEntries, actualVisitedEntries);
        }
        public void Clear()
        {
            _trace.WriteLine("Clearing all cached authorities...");

            IGitConfiguration config = _git.GetConfiguration();

            var orgKeys = new HashSet <string>(GitConfigurationKeyComparer.Instance);

            config.Enumerate(
                GitConfigurationLevel.Global,
                Constants.GitConfiguration.Credential.SectionName,
                AzureDevOpsConstants.GitConfiguration.Credential.AzureAuthority,
                entry =>
            {
                if (GitConfigurationKeyComparer.TrySplit(entry.Key, out _, out string scope, out _) &&
                    Uri.TryCreate(scope, UriKind.Absolute, out Uri orgUrn) &&
                    orgUrn.Scheme == AzureDevOpsConstants.UrnScheme)
                {
                    orgKeys.Add(entry.Key);
                }

                return(true);
            });
        public void GitConfiguration_Enumerate_CallbackReturnsFalse_InvokesCallbackForEachEntryUntilReturnsFalse()
        {
            string repoPath = CreateRepository(out string workDirPath);

            Git(repoPath, workDirPath, "config --local foo.name lancelot").AssertSuccess();
            Git(repoPath, workDirPath, "config --local foo.quest seek-holy-grail").AssertSuccess();
            Git(repoPath, workDirPath, "config --local foo.favcolor blue").AssertSuccess();

            var expectedVisitedEntries = new List <(string name, string value)>
            {
                ("foo.name", "lancelot"),
                ("foo.quest", "seek-holy-grail")
            };

            string            gitPath = GetGitPath();
            var               trace   = new NullTrace();
            var               git     = new GitProcess(trace, gitPath, repoPath);
            IGitConfiguration config  = git.GetConfiguration();

            var actualVisitedEntries = new List <(string name, string value)>();

            bool cb(string name, string value)
            {
                if (name.StartsWith("foo."))
                {
                    actualVisitedEntries.Add((name, value));
                }

                // Stop enumeration after 2 'foo' entries
                return(actualVisitedEntries.Count < 2);
            }

            config.Enumerate(cb);

            Assert.Equal(expectedVisitedEntries, actualVisitedEntries);
        }
예제 #6
0
        /// <summary>
        /// Try and get the all values of a specified setting as specified in the environment and Git configuration,
        /// in the correct order or precedence.
        /// </summary>
        /// <param name="envarName">Optional environment variable name.</param>
        /// <param name="section">Optional Git configuration section name.</param>
        /// <param name="property">Git configuration property name. Required if <paramref name="section"/> is set, optional otherwise.</param>
        /// <returns>All values for the specified setting, in order of precedence, or an empty collection if no such values are set.</returns>
        public IEnumerable <string> GetSettingValues(string envarName, string section, string property)
        {
            string value;

            if (envarName != null)
            {
                if (_environment.Variables.TryGetValue(envarName, out value))
                {
                    yield return(value);
                }
            }

            if (section != null && property != null)
            {
                IGitConfiguration config = GetGitConfiguration();

                if (RemoteUri != null)
                {
                    /*
                     * Look for URL scoped "section" configuration entries, starting from the most specific
                     * down to the least specific (stopping before the TLD).
                     *
                     * In a divergence from standard Git configuration rules, we also consider matching URL scopes
                     * without a scheme ("protocol://").
                     *
                     * For each level of scope, we look for an entry with the scheme included (the default), and then
                     * also one without it specified. This allows you to have one configuration scope for both "http" and
                     * "https" without needing to repeat yourself, for example.
                     *
                     * For example, starting with "https://foo.example.com/bar/buzz" we have:
                     *
                     *   1a. [section "https://foo.example.com/bar/buzz"]
                     *          property = value
                     *
                     *   1b. [section "foo.example.com/bar/buzz"]
                     *          property = value
                     *
                     *   2a. [section "https://foo.example.com/bar"]
                     *          property = value
                     *
                     *   2b. [section "foo.example.com/bar"]
                     *          property = value
                     *
                     *   3a. [section "https://foo.example.com"]
                     *          property = value
                     *
                     *   3b. [section "foo.example.com"]
                     *          property = value
                     *
                     *   4a. [section "https://example.com"]
                     *          property = value
                     *
                     *   4b. [section "example.com"]
                     *          property = value
                     *
                     */

                    // Enumerate all configuration entries with the correct section and property name
                    // and make a local copy of them here to avoid needing to call `TryGetValue` on the
                    // IGitConfiguration object multiple times in a loop below.
                    var configEntries = new Dictionary <string, string>();
                    config.Enumerate((entryName, entryValue) =>
                    {
                        string entrySection  = entryName.TruncateFromIndexOf('.');
                        string entryProperty = entryName.TrimUntilLastIndexOf('.');

                        if (StringComparer.OrdinalIgnoreCase.Equals(entrySection, section) &&
                            StringComparer.OrdinalIgnoreCase.Equals(entryProperty, property))
                        {
                            configEntries[entryName] = entryValue;
                        }

                        // Continue the enumeration
                        return(true);
                    });

                    foreach (string scope in RemoteUri.GetGitConfigurationScopes())
                    {
                        string queryName = $"{section}.{scope}.{property}";
                        // Look for a scoped entry that includes the scheme "protocol://example.com" first as this is more specific
                        if (configEntries.TryGetValue(queryName, out value))
                        {
                            yield return(value);
                        }

                        // Now look for a scoped entry that omits the scheme "example.com" second as this is less specific
                        string scopeWithoutScheme  = scope.TrimUntilIndexOf(Uri.SchemeDelimiter);
                        string queryWithSchemeName = $"{section}.{scopeWithoutScheme}.{property}";
                        if (configEntries.TryGetValue(queryWithSchemeName, out value))
                        {
                            yield return(value);
                        }
                    }
                }

                /*
                 * Try to look for an un-scoped "section" property setting:
                 *
                 *    [section]
                 *        property = value
                 *
                 */
                if (config.TryGetValue($"{section}.{property}", out value))
                {
                    yield return(value);
                }
            }
        }
        public IEnumerable <string> GetSettingValues(string envarName, string section, string property, bool isPath)
        {
            string value;

            if (envarName != null)
            {
                if (_environment.Variables.TryGetValue(envarName, out value))
                {
                    yield return(value);
                }
            }

            if (section != null && property != null)
            {
                IGitConfiguration config = _git.GetConfiguration();

                if (RemoteUri != null)
                {
                    /*
                     * Look for URL scoped "section" configuration entries, starting from the most specific
                     * down to the least specific (stopping before the TLD).
                     *
                     * In a divergence from standard Git configuration rules, we also consider matching URL scopes
                     * without a scheme ("protocol://").
                     *
                     * For each level of scope, we look for an entry with the scheme included (the default), and then
                     * also one without it specified. This allows you to have one configuration scope for both "http" and
                     * "https" without needing to repeat yourself, for example.
                     *
                     * For example, starting with "https://foo.example.com/bar/buzz" we have:
                     *
                     *   1a. [section "https://foo.example.com/bar/buzz"]
                     *          property = value
                     *
                     *   1b. [section "foo.example.com/bar/buzz"]
                     *          property = value
                     *
                     *   2a. [section "https://foo.example.com/bar"]
                     *          property = value
                     *
                     *   2b. [section "foo.example.com/bar"]
                     *          property = value
                     *
                     *   3a. [section "https://foo.example.com"]
                     *          property = value
                     *
                     *   3b. [section "foo.example.com"]
                     *          property = value
                     *
                     *   4a. [section "https://example.com"]
                     *          property = value
                     *
                     *   4b. [section "example.com"]
                     *          property = value
                     *
                     * It is also important to note that although the section and property names are NOT case
                     * sensitive, the "scope" part IS case sensitive! We must be careful when searching to ensure
                     * we follow Git's rules.
                     *
                     */

                    // Enumerate all configuration entries with the correct section and property name
                    // and make a local copy of them here to avoid needing to call `TryGetValue` on the
                    // IGitConfiguration object multiple times in a loop below.
                    var configEntries = new Dictionary <string, string>(GitConfigurationKeyComparer.Instance);
                    config.Enumerate(section, property, entry =>
                    {
                        configEntries[entry.Key] = entry.Value;

                        // Continue the enumeration
                        return(true);
                    });

                    foreach (string scope in RemoteUri.GetGitConfigurationScopes())
                    {
                        string queryName = $"{section}.{scope}.{property}";
                        // Look for a scoped entry that includes the scheme "protocol://example.com" first as
                        // this is more specific. If `isPath` is true, then re-get the value from the
                        // `GitConfiguration` with `isPath` specified.
                        if (configEntries.TryGetValue(queryName, out value) &&
                            (!isPath || config.TryGet(queryName, isPath, out value)))
                        {
                            yield return(value);
                        }

                        // Now look for a scoped entry that omits the scheme "example.com" second as this is less
                        // specific. As above, if `isPath` is true, get the configuration setting again with
                        // `isPath` specified.
                        string scopeWithoutScheme  = scope.TrimUntilIndexOf(Uri.SchemeDelimiter);
                        string queryWithSchemeName = $"{section}.{scopeWithoutScheme}.{property}";
                        if (configEntries.TryGetValue(queryWithSchemeName, out value) &&
                            (!isPath || config.TryGet(queryWithSchemeName, isPath, out value)))
                        {
                            yield return(value);
                        }
                    }
                }

                /*
                 * Try to look for an un-scoped "section" property setting:
                 *
                 *    [section]
                 *        property = value
                 *
                 */
                if (config.TryGet($"{section}.{property}", isPath, out value))
                {
                    yield return(value);
                }

                // Check for an externally specified default value
                if (TryGetExternalDefault(section, property, out string defaultValue))
                {
                    yield return(defaultValue);
                }
            }
        }