File: Settings\Items\RepositoryItem.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Configuration\NuGet.Configuration.csproj (NuGet.Configuration)
// Copyright(c) .NET Foundation.All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using NuGet.Shared;

namespace NuGet.Configuration
{
    public sealed class RepositoryItem : TrustedSignerItem
    {
        public override string ElementName => ConfigurationConstants.Repository;

        public string ServiceIndex => Attributes[ConfigurationConstants.ServiceIndex];

        public new string Name
        {
            get => base.Name;
            set => SetName(value);
        }

        private OwnersItem? _owners;

        public IList<string> Owners { get; private set; }

        protected override IReadOnlyCollection<string> RequiredAttributes { get; }
            = new HashSet<string>(new[] { ConfigurationConstants.NameAttribute, ConfigurationConstants.ServiceIndex });

        public RepositoryItem(string name, string serviceIndex, params CertificateItem[] certificates)
            : this(name, serviceIndex, owners: null, certificates: certificates)
        {
        }

        public RepositoryItem(string name, string serviceIndex, string? owners, params CertificateItem[] certificates)
            : base(name, certificates)
        {
            if (string.IsNullOrEmpty(serviceIndex))
            {
                throw new ArgumentNullException(nameof(serviceIndex));
            }

            AddAttribute(ConfigurationConstants.ServiceIndex, serviceIndex);

            if (!string.IsNullOrEmpty(owners))
            {
                _owners = new OwnersItem(owners!);
                Owners = _owners.Content;
            }
            else
            {
                _owners = null;
                Owners = new List<string>();
            }
        }

        internal RepositoryItem(XElement element, SettingsFile origin)
            : this(element, origin, ParseDescendants(element, origin))
        {
        }

        internal RepositoryItem(XElement element, SettingsFile origin, IEnumerable<SettingBase> parsedDescendants)
            : base(element, origin, parsedDescendants)
        {
            var parsedOwners = parsedDescendants.OfType<OwnersItem>();

            if (parsedOwners != null && parsedOwners.Any())
            {
                if (parsedOwners.Count() > 1)
                {
                    throw new NuGetConfigurationException(
                        string.Format(CultureInfo.CurrentCulture, Resources.UserSettings_UnableToParseConfigFile,
                            string.Format(CultureInfo.CurrentCulture, Resources.RepositoryMustHaveOneOwners, Name, ServiceIndex),
                            origin.ConfigFilePath));
                }

                _owners = parsedOwners.FirstOrDefault();
                Owners = _owners?.Content ?? new List<string>();
            }
            else
            {
                Owners = new List<string>();
            }
        }

        public override SettingBase Clone()
        {
            var newItem = new RepositoryItem(
                Name,
                ServiceIndex,
                string.Join(OwnersItem.OwnersListSeparator.ToString(CultureInfo.CurrentCulture), Owners),
                Certificates.Select(c => (CertificateItem)c.Clone()).ToArray());

            if (Origin != null)
            {
                newItem.SetOrigin(Origin);
            }

            return newItem;
        }

        internal override XNode AsXNode()
        {
            if (Node is XElement)
            {
                return Node;
            }

            var element = new XElement(ElementName);

            foreach (var cert in Certificates)
            {
                element.Add(cert.AsXNode());
            }

            if (_owners != null)
            {
                element.Add(_owners.AsXNode());
            }

            foreach (var attr in Attributes)
            {
                element.SetAttributeValue(attr.Key, attr.Value);
            }

            return element;
        }

        public override bool Equals(object? other)
        {
            if (other is RepositoryItem repository)
            {
                if (ReferenceEquals(this, repository))
                {
                    return true;
                }

                return string.Equals(ServiceIndex, repository.ServiceIndex, StringComparison.Ordinal);
            }

            return false;
        }

        public override int GetHashCode()
        {
            var combiner = new HashCodeCombiner();

            combiner.AddObject(Name);
            combiner.AddObject(ServiceIndex);

            return combiner.CombinedHash;
        }

        internal override void SetOrigin(SettingsFile origin)
        {
            base.SetOrigin(origin);

            _owners?.SetOrigin(origin);
        }

        internal override void RemoveFromSettings()
        {
            base.RemoveFromSettings();

            _owners?.RemoveFromSettings();
        }

        internal override void Update(SettingItem otherRepository)
        {
            base.Update(otherRepository);

            var other = (RepositoryItem)otherRepository;

            if (!Owners.SequenceEqual(other.Owners, StringComparer.Ordinal))
            {
                if (_owners == null || !Owners.Any())
                {
                    _owners = new OwnersItem(string.Join(OwnersItem.OwnersListSeparator.ToString(CultureInfo.CurrentCulture), other.Owners));
                    Owners = _owners.Content;

                    if (Origin != null)
                    {
                        _owners.SetOrigin(Origin);

                        if (Node != null)
                        {
                            _owners.SetNode(_owners.AsXNode());

                            XElementUtility.AddIndented(Node as XElement, _owners.Node);
                            Origin.IsDirty = true;
                        }
                    }
                }
                else if (!other.Owners.Any())
                {
                    XElementUtility.RemoveIndented(_owners.Node);
                    _owners = null;
                    Owners.Clear();

                    if (Origin != null)
                    {
                        Origin.IsDirty = true;
                    }
                }
                else
                {
#pragma warning disable CS8604 // Possible null reference argument.
                    // While adding nullable annotations to the codebase, the compiler is warning about this line, but after a long time
                    // trying to understand this code, I can't convince myself that this is safe. It's also not obvious to me how to fix it.
                    // Since this code has existed for years, apparently without causing NullReferenceExeptions, I'm going to suppress the warning
                    // until someone can refactor this code to make it safe.
                    _owners.Update(other._owners);
#pragma warning restore CS8604 // Possible null reference argument.
                    Owners = _owners.Content;
                }
            }
        }
    }
}