private void GulpCallback(object sender, EventArgs e)
        {
            var cmd  = (OleMenuCommand)sender;
            var text = cmd.Text;
            var task = text.Substring(text.IndexOf(':') + 1).Trim();

            if (task == "Gulp")
            {
                task = "";
            }

            // if the command is checked it means that there is a running grunt task associated
            // so we kill it
            if (cmd.Checked)
            {
                System.Diagnostics.Process pro;
                processes.TryGetValue(cmd, out pro);
                if (pro != null)
                {
                    OutputHelpers.Output("Stopping process " + cmd.Text);
                    ProcessHelpers.KillProcessAndChildren(pro.Id);
                    processes.Remove(cmd);
                }
            }

            if (!cmd.Checked)
            {
                // launches the grunt process and redirects the output to the output window
                RunProcess(cmd, " /c \"gulp --no-color " + task + "  2>&1 \" ", false);
            }
            else
            {
                cmd.Checked = false;
            }
        }
        /// <summary>
        /// Retrieve information about the default association between builds of a specific branch/repo
        /// and a channel.
        /// </summary>
        /// <returns></returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                IEnumerable <DefaultChannel> defaultChannels = (await remote.GetDefaultChannelsAsync()).Where(defaultChannel =>
                {
                    return((string.IsNullOrEmpty(_options.SourceRepository) ||
                            defaultChannel.Repository.Contains(_options.SourceRepository, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Branch) ||
                            defaultChannel.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase)) &&
                           (string.IsNullOrEmpty(_options.Channel) ||
                            defaultChannel.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)));
                });

                if (defaultChannels.Count() == 0)
                {
                    Console.WriteLine("No matching channels were found.");
                }

                // Write out a simple list of each channel's name
                foreach (DefaultChannel defaultChannel in defaultChannels)
                {
                    Console.WriteLine(OutputHelpers.GetDefaultChannelDescriptionString(defaultChannel));
                }

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve default channel information.");
                return(Constants.ErrorCode);
            }
        }
        /// <summary>
        ///     Get a specific build of a repository
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                Build build = await remote.GetBuildAsync(_options.Id);

                if (build != null)
                {
                    OutputHelpers.PrintBuild(build);
                }
                else
                {
                    Console.WriteLine($"Could not find build with id '{_options.Id}'");
                    return(Constants.ErrorCode);
                }

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to retrieve build with id '{_options.Id}'");
                return(Constants.ErrorCode);
            }
        }
Example #4
0
        private void UpdateDialogue(DialogueScreen screen)
        {
            _output.Text = OutputHelpers.ProcessOutput(screen.Says.Trim());

            _choices.Items.Clear();
            _choices.Items.AddRange(
                screen.Choices.Where(c => string.IsNullOrEmpty(c.DisplayCondition) ||
                                     EmbeddedFunctionsHelper.Conditional(c.DisplayCondition).Success));
        }
Example #5
0
 private async Task LogDependencyGraph(DependencyGraph graph)
 {
     using (StreamWriter writer = OutputHelpers.GetOutputFileStreamOrConsole(_options.OutputFile))
     {
         await writer.WriteLineAsync($"Repositories:");
         await LogDependencyGraphNode(writer, graph.Root, "  ");
         await LogIncoherencies(writer, graph);
     }
 }
 private static void PrintCounteragentList(CounteragentList counteragentList, string title)
 {
     System.Console.WriteLine();
     System.Console.WriteLine(string.Format("{0} ({1}/{2}): ", title, counteragentList.Counteragents.Count, counteragentList.TotalCount));
     foreach (var counteragent in counteragentList.Counteragents)
     {
         System.Console.WriteLine(string.Format("  {0}", OutputHelpers.FormatOrganization(counteragent.Organization)));
     }
     System.Console.WriteLine();
 }
Example #7
0
 private static void PrintCounteragentList(CounteragentList counteragentList, string title)
 {
     System.Console.WriteLine();
     System.Console.WriteLine($"{title} ({counteragentList.Counteragents.Count}/{counteragentList.TotalCount}): ");
     foreach (var counteragent in counteragentList.Counteragents)
     {
         OutputHelpers.PrintOrganization(counteragent.Organization);
     }
     System.Console.WriteLine();
 }
Example #8
0
        /// <summary>
        ///     Resolve channel based on the input options. If no channel could be resolved
        ///     based on the input options, returns null.
        /// </summary>
        /// <returns>Default channel or null</returns>
        protected async Task <DefaultChannel> ResolveSingleChannel()
        {
            IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

            IEnumerable <DefaultChannel> potentialDefaultChannels = await remote.GetDefaultChannelsAsync();

            // User should have supplied id or a combo of the channel name, repo, and branch.
            if (_options.Id != -1)
            {
                DefaultChannel defaultChannel = potentialDefaultChannels.SingleOrDefault(d => d.Id == _options.Id);
                if (defaultChannel == null)
                {
                    Console.WriteLine($"Could not find a default channel with id {_options.Id}");
                }
                return(defaultChannel);
            }
            else if (string.IsNullOrEmpty(_options.Repository) ||
                     string.IsNullOrEmpty(_options.Channel) ||
                     string.IsNullOrEmpty(_options.Branch))
            {
                Console.WriteLine("Please specify either the default channel id with --id or a combination of --channel, --branch and --repo");
                return(null);
            }

            // Otherwise, filter based on the other inputs. If more than one resolves, then print the possible
            // matches and return null
            var matchingChannels = potentialDefaultChannels.Where(d =>
            {
                return((string.IsNullOrEmpty(_options.Repository) || d.Repository.Contains(_options.Repository, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Channel) || d.Channel.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase)) &&
                       (string.IsNullOrEmpty(_options.Branch) || d.Branch.Contains(_options.Branch, StringComparison.OrdinalIgnoreCase)));
            });

            if (!matchingChannels.Any())
            {
                Console.WriteLine($"No channels found matching the specified criteria.");
                return(null);
            }
            else if (matchingChannels.Count() != 1)
            {
                Console.WriteLine($"More than one channel matching the specified criteria. Please change your options to be more specific.");
                foreach (DefaultChannel defaultChannel in matchingChannels)
                {
                    Console.WriteLine($"    {OutputHelpers.GetDefaultChannelDescriptionString(defaultChannel)}");
                }
                return(null);
            }
            else
            {
                return(matchingChannels.Single());
            }
        }
Example #9
0
        protected override void PerformRunCommand(string[] args)
        {
            var boxId = args.Length > 0 ? args[0] : null;

            if (boxId == null)
            {
                OutputHelpers.ShowBoxes(ConsoleContext);
                return;
            }
            ConsoleContext.CurrentBoxId = InputHelpers.AutocompleteBoxId(ConsoleContext, boxId);
            System.Console.WriteLine("Текущий ящик: " + ConsoleContext.CurrentBoxId);
            ConsoleContext.Events = null;
        }
Example #10
0
        protected override void PerformRunCommand(string[] args)
        {
            System.Console.Write("Login: "******"Password: "******"Аутентификация пройдена.");
            OutputHelpers.ShowBoxes(ConsoleContext);
        }
Example #11
0
        /// <summary>
        ///     Log the dependency graph as a simple flat list of repo/sha combinations
        ///     that contribute to this graph.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        private async Task LogFlatDependencyGraph(DependencyGraph graph)
        {
            using (StreamWriter writer = OutputHelpers.GetOutputFileStreamOrConsole(_options.OutputFile))
            {
                await writer.WriteLineAsync($"Repositories:");

                foreach (DependencyGraphNode node in graph.Nodes)
                {
                    await LogBasicNodeDetails(writer, node, "  ");
                }
                await LogIncoherencies(writer, graph);
            }
        }
        private static void RunProcess(OleMenuCommand cmd, string argument, bool fromRoot)
        {
            dte.StatusBar.Animate(true, vsStatusAnimation.vsStatusAnimationBuild);

            try
            {
                System.Diagnostics.ProcessStartInfo procStartInfo = new ProcessStartInfo()
                {
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    StandardOutputEncoding = Encoding.UTF8,
                    StandardErrorEncoding  = Encoding.UTF8,
                    UseShellExecute        = false,
                    CreateNoWindow         = true,
                    WorkingDirectory       = fromRoot ? SolutionHelpers.GetRootFolder(dte) : Path.GetDirectoryName(SolutionHelpers.GetSourceFilePath()),
                    FileName  = "cmd",
                    Arguments = argument,
                };

                System.Diagnostics.Process proc = new System.Diagnostics.Process()
                {
                    StartInfo           = procStartInfo,
                    EnableRaisingEvents = true
                };

                OutputHelpers.Output("Executing " + cmd.Text + " \r\n\r\n", true);

                proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) => OutputHelpers.Output(outLine.Data + "\r\n");
                proc.ErrorDataReceived  += (object sendingProcess, DataReceivedEventArgs outLine) => OutputHelpers.Output(outLine.Data + "\r\n");
                proc.Exited             += (x, y) =>
                {
                    processes.Remove(cmd);
                    cmd.Checked = false;
                    dte.StatusBar.Animate(false, vsStatusAnimation.vsStatusAnimationBuild);
                };

                proc.Start();

                proc.BeginOutputReadLine();
                proc.BeginErrorReadLine();

                cmd.Checked = true;

                processes.Add(cmd, proc);
            }
            catch (Exception ex)
            {
                OutputHelpers.Output(ex.Message);
            }
        }
Example #13
0
        /// <summary>
        ///     Assigns a build to a channel.
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                // Find the build to give someone info
                Build build = await remote.GetBuildAsync(_options.Id);

                if (build == null)
                {
                    Console.WriteLine($"Could not find a build with id '{_options.Id}'");
                    return(Constants.ErrorCode);
                }

                Channel targetChannel = await UxHelpers.ResolveSingleChannel(remote, _options.Channel);

                if (targetChannel == null)
                {
                    return(Constants.ErrorCode);
                }

                if (build.Channels.Any(c => c.Id == targetChannel.Id))
                {
                    Console.WriteLine($"Build '{build.Id}' has already been assigned to '{targetChannel.Name}'");
                    return(Constants.SuccessCode);
                }

                Console.WriteLine($"Assigning the following build to channel '{targetChannel.Name}':");
                Console.WriteLine();
                OutputHelpers.PrintBuild(build);

                await remote.AssignBuildToChannel(_options.Id, targetChannel.Id);

                // Be helpful. Let the user know what will happen.
                string buildRepo = build.GitHubRepository ?? build.AzureDevOpsRepository;
                List <Subscription> applicableSubscriptions = (await remote.GetSubscriptionsAsync(
                                                                   sourceRepo: buildRepo, channelId: targetChannel.Id)).ToList();

                PrintSubscriptionInfo(applicableSubscriptions);

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error: Failed to assign build '{_options.Id}' to channel '{_options.Channel}'.");
                return(Constants.ErrorCode);
            }
        }
Example #14
0
		protected override void PerformRunCommand(string[] args)
		{
			System.Console.Write("ИНН: ");
			var inn = System.Console.ReadLine();
			if (String.IsNullOrWhiteSpace(inn))
			{
				System.Console.WriteLine("Нельзя искать по пустому ИНН");
				return;
			}

			System.Console.Write("КПП (может быть пустым): ");
			var kpp = System.Console.ReadLine();
			var foundOrgs = ConsoleContext.DiadocApi.GetOrganizationsByInnKpp(inn, kpp).Organizations;
			System.Console.WriteLine(foundOrgs.Count == 0 ? "Организаций не найдено" : "Список найденных организаций:");
			OutputHelpers.PrintBoxes(foundOrgs);
			ConsoleContext.Boxes = foundOrgs.SelectMany(o => o.Boxes).ToList();
		}
Example #15
0
        private static void RunTool(EnvironmentParameters environment)
        {
            var resourceManagerDataProvider = new ResourceManagerDataProvider(environment: environment);

            string authorizationHeader = null;

            if (environment.ProvidedSubscriptionId.IsGuid())
            {
                var tenantId = resourceManagerDataProvider
                               .GetSubscriptionTenantId(subscriptionId: environment.ProvidedSubscriptionId)
                               .Result;

                authorizationHeader = new AuthenticationProvider(environment: environment)
                                      .GetAuthorizationResult(tenantId: tenantId)
                                      .CreateAuthorizationHeader();
            }
            else
            {
                var tenants = resourceManagerDataProvider.GetTenantsWithSubscriptions().Result;
                authorizationHeader = OutputHelpers.PromptForTokenSelection(tenants);
            }

            OutputHelpers.OutputToken(outputMethod: environment.OutputMethod, token: authorizationHeader);
        }
        protected override void PerformRunCommand(string[] args)
        {
            var thumbprint = args.Length > 0 ? args[0] : null;

            if (thumbprint == null)
            {
                ShowCertificates();
            }
            else
            {
                ConsoleContext.CurrentCert = FindCertificate(thumbprint);
                if (ConsoleContext.CurrentCert != null)
                {
                    ConsoleContext.CurrentToken = ConsoleContext.DiadocApi.Authenticate(ConsoleContext.CurrentCert.RawData);
                    ConsoleContext.ClearAuthenticationContext();
                    System.Console.WriteLine("Аутентификация пройдена. Сертификат: " + CertificateToString(ConsoleContext.CurrentCert));
                    OutputHelpers.ShowBoxes(ConsoleContext);
                }
                else
                {
                    System.Console.WriteLine("Аутентификация не пройдена: не найден сертификат");
                }
            }
        }
        /// <summary>
        /// Choose character etc
        /// </summary>
        private static void StartFromNewGame()
        {
            AppState.IO.Write("Choose one of the following characters:");

            var startChars = (
                from id in AppState.Project.StartInfo.CharacterIds
                select AppState.Project.Characters.GetId(id)
                ).ToList();

            foreach (var c in startChars)
            {
                OutputHelpers.PrintCharacter(c);
                AppState.IO.Write("");
            }

            AppState.IO.GetChoice(startChars, c => c.Name, choosenChar =>
            {
                AppState.Game.PlayerChar = choosenChar;
                AppState.IO.Write($">> {choosenChar.Name}\n");
                AppState.IO.Write(AppState.Project.StartInfo.IntroText
                                  ?? "The project does not have an intro text");
                AppState.Game.CurrentDialog.Start();
            });
        }
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///  
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///  
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphViz(Channel targetChannel, DependencyFlowGraph graph)
        {
            StringBuilder subgraphClusterWriter = null;
            bool writeToSubgraphCluster = targetChannel != null;
            if (writeToSubgraphCluster)
            {
                subgraphClusterWriter = new StringBuilder();
                subgraphClusterWriter.AppendLine($"    subgraph cluster_{OutputHelpers.CalculateGraphVizNodeName(targetChannel.Name)} {{");
                subgraphClusterWriter.AppendLine($"        label = \"{targetChannel.Name}\"");
            }

            using (StreamWriter writer = OutputHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");
                await writer.WriteLineAsync("    node [shape=record]");
                foreach (DependencyFlowNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    // First add the node name
                    nodeBuilder.Append($"    {OutputHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append("[label=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(OutputHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append branch name
                    nodeBuilder.Append(node.Branch);

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // If highlighting a specific channel, Add those nodes to a subgraph cluster
                    // if they output to the subgraph cluster.
                    if (writeToSubgraphCluster && node.OutputChannels.Any(c => c == targetChannel.Name))
                    {
                        subgraphClusterWriter.AppendLine($"        {OutputHelpers.CalculateGraphVizNodeName(node)}");
                    }

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());
                }

                // Now write all the edges
                foreach (DependencyFlowEdge edge in graph.Edges)
                {
                    string fromNode = OutputHelpers.CalculateGraphVizNodeName(edge.From);
                    string toNode = OutputHelpers.CalculateGraphVizNodeName(edge.To);
                    string label = $"{edge.Subscription.Channel.Name} ({edge.Subscription.Policy.UpdateFrequency})";

                    if (writeToSubgraphCluster && edge.Subscription.Channel.Name == targetChannel.Name)
                    {
                        subgraphClusterWriter.AppendLine($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                    else
                    {
                        await writer.WriteLineAsync($"    {fromNode} -> {toNode} [{GetEdgeStyle(edge)}]");
                    }
                }

                if (writeToSubgraphCluster)
                {
                    await writer.WriteLineAsync(subgraphClusterWriter.ToString());
                    await writer.WriteLineAsync("    }");
                }

                // Write a legend

                await writer.WriteLineAsync("    subgraph cluster1 {");
                await writer.WriteLineAsync("        rankdir=RL;");
                await writer.WriteLineAsync("        label = \"Legend\"");
                await writer.WriteLineAsync("        shape = rectangle;");
                await writer.WriteLineAsync("        color = black;");
                await writer.WriteLineAsync("        a[style = invis];");
                await writer.WriteLineAsync("        b[style = invis];");
                await writer.WriteLineAsync("        c[style = invis];");
                await writer.WriteLineAsync("        d[style = invis];");
                await writer.WriteLineAsync("        e[style = invis];");
                await writer.WriteLineAsync("        f[style = invis];");
                await writer.WriteLineAsync("        c->d[label = \"Updated Every Build\", style = bold];");
                await writer.WriteLineAsync("        a->b[label = \"Updated Every Day\", style = dashed];");
                await writer.WriteLineAsync("        e->f[label = \"Disabled/Updated On-demand\", style = dotted];");
                await writer.WriteLineAsync("    }");
            
                await writer.WriteLineAsync("}");
            }
        }
Example #19
0
 private void HandleUsageException(UsageException usageException)
 {
     System.Console.WriteLine("Неправильный синтакс комманды:");
     System.Console.WriteLine(OutputHelpers.FormatCommandSyntax(this));
 }
Example #20
0
 public void Handle()
 {
     Game.CurrentScene = this;
     IO.Write($">> You are now in {this.Name}. You look around and see:");
     OutputHelpers.LookAround();
 }
 private static void ShowInventory(string _)
 {
     OutputHelpers.PrintInventory();
     IO.GetTextInput();
 }
Example #22
0
 private void UpdateDisplay()
 {
     output.SetOutput(OutputHelpers.ProcessOutput(GameManager.Instance.ActiveScene.Text));
 }
 private static void LookAround(string _)
 {
     OutputHelpers.LookAround();
     IO.GetTextInput();
 }
Example #24
0
        /// <summary>
        ///     Gets the latest build for a repo
        /// </summary>
        /// <returns>Process exit code.</returns>
        public override async Task <int> ExecuteAsync()
        {
            try
            {
                IRemote remote = RemoteFactory.GetBarOnlyRemote(_options, Logger);

                // Calculate out possible repos based on the input strings.
                // Today the DB has no way of searching for builds by substring, so for now
                // grab source/targets repos of subscriptions matched on substring,
                // and then add the explicit repo from the options.
                // Then search channels by substring
                // Then run GetLatestBuild for each permutation.

                var subscriptions = await remote.GetSubscriptionsAsync();

                var possibleRepos = subscriptions
                                    .SelectMany(subscription => new List <string> {
                    subscription.SourceRepository, subscription.TargetRepository
                })
                                    .Where(r => r.Contains(_options.Repo, StringComparison.OrdinalIgnoreCase))
                                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
                possibleRepos.Add(_options.Repo);

                var channels = (await remote.GetChannelsAsync())
                               .Where(c => string.IsNullOrEmpty(_options.Channel) || c.Name.Contains(_options.Channel, StringComparison.OrdinalIgnoreCase));

                if (!channels.Any())
                {
                    Console.WriteLine($"Could not find a channel with name containing '{_options.Channel}'");
                    return(Constants.ErrorCode);
                }

                bool foundBuilds = false;
                foreach (string possibleRepo in possibleRepos)
                {
                    foreach (Channel channel in channels)
                    {
                        Build latestBuild = await remote.GetLatestBuildAsync(possibleRepo, channel.Id);

                        if (latestBuild != null)
                        {
                            if (foundBuilds)
                            {
                                Console.WriteLine();
                            }
                            foundBuilds = true;
                            OutputHelpers.PrintBuild(latestBuild);
                        }
                    }
                }

                if (!foundBuilds)
                {
                    Console.WriteLine("No latest build found matching the specified criteria");
                    return(Constants.ErrorCode);
                }

                return(Constants.SuccessCode);
            }
            catch (Exception e)
            {
                Logger.LogError(e, "Error: Failed to retrieve latest build.");
                return(Constants.ErrorCode);
            }
        }
 protected override void PerformRunCommand(string[] args)
 {
     OutputHelpers.ShowBoxes(ConsoleContext);
 }
Example #26
0
        /// <summary>
        ///     Log the graph in graphviz (dot) format.
        /// </summary>
        /// <param name="graph">Graph to log</param>
        /// <remarks>
        /// Example of a graphviz graph description
        ///
        /// digraph graphname {
        ///    a -> b -> c;
        ///    b -> d;
        /// }
        ///
        /// For more info see https://www.graphviz.org/
        /// </remarks>
        /// <returns>Async task</returns>
        private async Task LogGraphViz(DependencyGraph graph)
        {
            using (StreamWriter writer = OutputHelpers.GetOutputFileStreamOrConsole(_options.GraphVizOutputFile))
            {
                await writer.WriteLineAsync("digraph repositoryGraph {");

                await writer.WriteLineAsync("    node [shape=record]");

                foreach (DependencyGraphNode node in graph.Nodes)
                {
                    StringBuilder nodeBuilder = new StringBuilder();

                    // First add the node name
                    nodeBuilder.Append($"    {OutputHelpers.CalculateGraphVizNodeName(node)}");

                    // Then add the label.  label looks like [label="<info here>"]
                    nodeBuilder.Append("[label=\"");

                    // Append friendly repo name
                    nodeBuilder.Append(OutputHelpers.GetSimpleRepoName(node.Repository));
                    nodeBuilder.Append(@"\n");

                    // Append short commit sha
                    nodeBuilder.Append(node.Commit.Substring(0, node.Commit.Length < 10 ? node.Commit.Length : 10));

                    // Append a build string (with newline) if available
                    if (node.ContributingBuilds != null && node.ContributingBuilds.Any())
                    {
                        Build newestBuild = node.ContributingBuilds.OrderByDescending(b => b.DateProduced).First();
                        nodeBuilder.Append($"\\n{newestBuild.DateProduced.ToString("g")} (UTC)");
                    }

                    // Append a diff string if the graph contains diff info.
                    GitDiff diffFrom = node.DiffFrom;
                    if (diffFrom != null)
                    {
                        if (!diffFrom.Valid)
                        {
                            nodeBuilder.Append("\\ndiff unknown");
                        }
                        else if (diffFrom.Ahead != 0 || diffFrom.Behind != 0)
                        {
                            if (node.DiffFrom.Ahead != 0)
                            {
                                nodeBuilder.Append($"\\nahead: {node.DiffFrom.Ahead} commits");
                            }
                            if (node.DiffFrom.Behind != 0)
                            {
                                nodeBuilder.Append($"\\nbehind: {node.DiffFrom.Behind} commits");
                            }
                        }
                        else
                        {
                            nodeBuilder.Append("\\nlatest");
                        }
                    }

                    // Append end of label and end of node.
                    nodeBuilder.Append("\"];");

                    // Write it out.
                    await writer.WriteLineAsync(nodeBuilder.ToString());

                    // Now write the edges
                    foreach (DependencyGraphNode childNode in node.Children)
                    {
                        await writer.WriteLineAsync($"    {OutputHelpers.CalculateGraphVizNodeName(node)} -> {OutputHelpers.CalculateGraphVizNodeName(childNode)}");
                    }
                }

                await writer.WriteLineAsync("}");
            }
        }