/// <summary> /// Verify that a branch exists in the specified repo and contains a version details file. /// If it does not, optionally prompt the user to confirm that they wish to continue. /// </summary> /// <param name="remote">Remote</param> /// <param name="repo">Repository that the branch should be in</param> /// <param name="branch">Branch to check the existence of</param> /// <param name="prompt">Prompt the user to verify that they want to continue</param> /// <returns>True if the branch exists, prompting is not desired, or if the user confirms that they want to continue. False otherwise.</returns> public static async Task <bool> VerifyAndConfirmBranchExistsAsync(IRemote remote, string repo, string branch, bool prompt) { const string regexPrefix = "-regex:"; // IRemote doesn't currently provide a way for enumerating all branches in a repo, and the purpose of supporting regex is to allow new ones to match // So in this case we'll just prompt if (branch.StartsWith(regexPrefix, StringComparison.InvariantCultureIgnoreCase)) { Console.WriteLine($"Warning: Regular expression '{branch.Substring(regexPrefix.Length)}' will be used to match on branches in '{repo}'."); Console.WriteLine("To ensure dependency updates (where desired), please verify all branches matching this pattern contain an eng/Version.Details.xml file."); return(!prompt || PromptForYesNo("Continue?")); } try { branch = GitHelpers.NormalizeBranchName(branch); await remote.GetDependenciesAsync(repo, branch); } catch (DependencyFileNotFoundException) { Console.WriteLine($"Warning: Could not find an eng/Version.Details.xml at '{repo}@{branch}'. Dependency updates may not happen as expected."); if (prompt) { return(PromptForYesNo("Continue?")); } } return(true); }
private static async Task CloneRemoteRepoAndDependencies(RemoteFactory remoteFactory, string reposFolder, string repoUri, string commit, bool includeToolset, ILogger logger) { IRemote rootRepoRemote = await remoteFactory.GetRemoteAsync(repoUri, logger); IEnumerable <DependencyDetail> rootDependencies = await rootRepoRemote.GetDependenciesAsync(repoUri, commit); rootDependencies = FilterToolsetDependencies(rootDependencies, includeToolset); if (!rootDependencies.Any()) { string repoPath = GetRepoDirectory(reposFolder, repoUri, commit); if (Directory.Exists(repoPath)) { logger.LogDebug($"Repo path {repoPath} already exists, assuming we cloned already and skipping"); } else { logger.LogInformation($"Remote repo {repoUri}@{commit} has no dependencies. Cloning shallowly into {repoPath}"); IRemote repoRemote = await remoteFactory.GetRemoteAsync(repoUri, logger); repoRemote.Clone(repoUri, commit, repoPath); } return; } DependencyGraphBuildOptions graphBuildOptions = new DependencyGraphBuildOptions() { IncludeToolset = includeToolset, LookupBuilds = true, NodeDiff = NodeDiff.None }; logger.LogDebug($"Building depdendency graph for {repoUri}@{commit} with {rootDependencies.Count()} dependencies"); DependencyGraph graph = await DependencyGraph.BuildRemoteDependencyGraphAsync( remoteFactory, rootDependencies, repoUri, commit, graphBuildOptions, logger); foreach (DependencyGraphNode repo in graph.Nodes) { string repoPath = GetRepoDirectory(reposFolder, repo.Repository, repo.Commit); if (Directory.Exists(repoPath)) { logger.LogDebug($"Repo path {repoPath} already exists, assuming we cloned already and skipping"); } else { logger.LogInformation($"Cloning {repo.Repository}@{repo.Commit} into {repoPath}"); IRemote repoRemote = await remoteFactory.GetRemoteAsync(repo.Repository, logger); repoRemote.Clone(repo.Repository, repo.Commit, repoPath); } } }
/// <summary> /// Evaluate the metric. /// </summary> /// <param name="remoteFactory">Remote factory</param> /// <returns>True if the metric passed, false otherwise</returns> public override async Task EvaluateAsync() { IRemote remote = await RemoteFactory.GetRemoteAsync(Repository, Logger); Logger.LogInformation("Evaluating subscription health metrics for {repo}@{branch}", Repository, Branch); // Get subscriptions that target this repo/branch Subscriptions = (await remote.GetSubscriptionsAsync(targetRepo: Repository)) .Where(s => s.TargetBranch.Equals(Branch, StringComparison.OrdinalIgnoreCase)).ToList(); // Get the dependencies of the repository/branch. Skip pinned and subscriptions tied to another // dependency (coherent parent), as well as those not selected by the dependency selector. try { Dependencies = (await remote.GetDependenciesAsync(Repository, Branch)) .Where(d => !d.Pinned && string.IsNullOrEmpty(d.CoherentParentDependencyName)) .Where(d => DependencySelector(d)) .ToList(); } catch (DependencyFileNotFoundException) { // When the dependency file is not found, then we're good as long as this repo is not // targeted by any subscriptions if (Subscriptions.Any()) { MissingVersionDetailsFile = true; Result = HealthResult.Failed; return; } else { Result = HealthResult.Passed; return; } } Dictionary <string, Subscription> latestAssets = await GetLatestAssetsAndComputeConflicts(remote); ComputeSubscriptionUse(latestAssets); // Determine the result. A conflict or missing subscription is an error. // A non-flowing subscription or unused subscription is a warning if (DependenciesMissingSubscriptions.Any() || ConflictingSubscriptions.Any()) { Result = HealthResult.Failed; } else if (UnusedSubscriptions.Any() || DependenciesThatDoNotFlow.Any()) { Result = HealthResult.Warning; } else { Result = HealthResult.Passed; } }
/// <summary> /// Verify that a branch exists in the specified repo and contains a version details file. /// If it does not, optionally prompt the user to confirm that they wish to continue. /// </summary> /// <param name="remote">Remote</param> /// <param name="repo">Repository that the branch should be in</param> /// <param name="branch">Branch to check the existence of</param> /// <param name="prompt">Prompt the user to verify that they want to continue</param> /// <returns>True if the branch exists, prompting is not desired, or if the user confirms that they want to continue. False otherwise.</returns> public static async Task <bool> VerifyAndConfirmBranchExistsAsync(IRemote remote, string repo, string branch, bool prompt) { try { var dependencies = await remote.GetDependenciesAsync(repo, branch); } catch (DependencyFileNotFoundException) { Console.WriteLine($"Warning: Could not find an eng/Version.Details.xml at '{repo}@{branch}'. Dependency updates may not happen as expected."); if (prompt) { return(PromptForYesNo("Continue?")); } } return(true); }
public override async Task <int> ExecuteAsync() { try { IEnumerable <DependencyDetail> rootDependencies = null; DependencyGraph graph; RemoteFactory remoteFactory = new RemoteFactory(_options); if (!_options.Local) { NodeDiff diffOption = NodeDiff.None; // Check node diff options switch (_options.DeltaFrom.ToLowerInvariant()) { case "none": break; case "newest-in-channel": diffOption = NodeDiff.LatestInChannel; break; case "newest-in-graph": diffOption = NodeDiff.LatestInGraph; break; default: Console.WriteLine("Unknown --delta-from option, please see help."); return(Constants.ErrorCode); } // If the repo uri and version are set, then call the graph // build operation based on those. Both should be set in this case. // If they are not set, then gather the initial set based on the local repository, // and then call the graph build with that root set. if (!string.IsNullOrEmpty(_options.RepoUri)) { if (string.IsNullOrEmpty(_options.Version)) { Console.WriteLine("If --repo is set, --version should be supplied"); return(Constants.ErrorCode); } Console.WriteLine($"Getting root dependencies from {_options.RepoUri}@{_options.Version}..."); // Grab root dependency set. The graph build can do this, but // if an original asset name is passed, then this will do the initial filtering. IRemote rootRepoRemote = await remoteFactory.GetRemoteAsync(_options.RepoUri, Logger); rootDependencies = await rootRepoRemote.GetDependenciesAsync( _options.RepoUri, _options.Version, _options.AssetName); } else { if (!string.IsNullOrEmpty(_options.Version)) { Console.WriteLine("If --version is supplied, then --repo is required"); return(Constants.ErrorCode); } Console.WriteLine($"Getting root dependencies from local repository..."); // Grab root dependency set from local repo Local local = new Local(Logger); rootDependencies = await local.GetDependenciesAsync( _options.AssetName); } Console.WriteLine($"Building repository dependency graph..."); rootDependencies = FilterToolsetDependencies(rootDependencies); if (!rootDependencies.Any()) { Console.WriteLine($"No root dependencies found, exiting."); return(Constants.ErrorCode); } DependencyGraphBuildOptions graphBuildOptions = new DependencyGraphBuildOptions() { IncludeToolset = _options.IncludeToolset, LookupBuilds = diffOption != NodeDiff.None || !_options.SkipBuildLookup, NodeDiff = diffOption }; // Build graph graph = await DependencyGraph.BuildRemoteDependencyGraphAsync( remoteFactory, rootDependencies, _options.RepoUri ?? LocalHelpers.GetRootDir(Logger), _options.Version ?? LocalHelpers.GetGitCommit(Logger), graphBuildOptions, Logger); } else { Console.WriteLine($"Getting root dependencies from local repository..."); Local local = new Local(Logger); rootDependencies = await local.GetDependenciesAsync( _options.AssetName); rootDependencies = FilterToolsetDependencies(rootDependencies); if (!rootDependencies.Any()) { Console.WriteLine($"No root dependencies found, exiting."); return(Constants.ErrorCode); } Console.WriteLine($"Building repository dependency graph from local information..."); DependencyGraphBuildOptions graphBuildOptions = new DependencyGraphBuildOptions() { IncludeToolset = _options.IncludeToolset, LookupBuilds = false, NodeDiff = NodeDiff.None }; // Build graph using only local resources graph = await DependencyGraph.BuildLocalDependencyGraphAsync( rootDependencies, graphBuildOptions, Logger, LocalHelpers.GetRootDir(Logger), LocalHelpers.GetGitCommit(Logger), _options.ReposFolder, _options.RemotesMap); } if (_options.Flat) { await LogFlatDependencyGraph(graph); } else { await LogDependencyGraph(graph); } if (!string.IsNullOrEmpty(_options.GraphVizOutputFile)) { await LogGraphViz(graph); } return(Constants.SuccessCode); } catch (Exception exc) { Logger.LogError(exc, "Something failed while getting the dependency graph."); return(Constants.ErrorCode); } }
private static async Task <IEnumerable <DependencyDetail> > GetDependenciesAsync( IRemoteFactory remoteFactory, bool remote, ILogger logger, string repoUri, string commit, bool includeToolset, IEnumerable <string> remotesMap, string reposFolder, string testPath = null) { try { IEnumerable <DependencyDetail> dependencies = null; if (!string.IsNullOrEmpty(testPath)) { testPath = Path.Combine( testPath, repoUri, commit); if (Directory.Exists(testPath)) { Local local = new Local(logger, testPath); dependencies = await local.GetDependenciesAsync(); } } else if (remote) { IRemote remoteClient = await remoteFactory.GetRemoteAsync(repoUri, logger); dependencies = await remoteClient.GetDependenciesAsync( repoUri, commit); } else { string repoPath = GetRepoPath(repoUri, commit, remotesMap, reposFolder, logger); if (!string.IsNullOrEmpty(repoPath)) { Local local = new Local(logger); string fileContents = LocalHelpers.GitShow( repoPath, commit, VersionFiles.VersionDetailsXml, logger); dependencies = local.GetDependenciesFromFileContents(fileContents); } } if (!includeToolset) { dependencies = dependencies.Where(dependency => dependency.Type != DependencyType.Toolset); } return(dependencies); } catch (DependencyFileNotFoundException) { // This is not an error. Dependencies can be specified with explicit shas that // may not have eng/Version.Details.xml at that point. logger.LogWarning($"{repoUri}@{commit} does not have an eng/Version.Details.xml."); return(null); } catch (Exception exc) { logger.LogError(exc, $"Something failed while trying the fetch the " + $"dependencies of repo '{repoUri}' at sha " + $"'{commit}'"); throw; } }