/// <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("}"); } }
/// <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("}"); } }