Skip to content

Commit

Permalink
Provide more context for component version diffs in text output
Browse files Browse the repository at this point in the history
- Display the component group
- Display unchanged versions when a component is included multiple times and one version has changed
  • Loading branch information
coderpatros committed Feb 16, 2021
1 parent 9138f55 commit e1af5ca
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 83 deletions.
132 changes: 57 additions & 75 deletions cyclonedx/Commands/Convert/DiffCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,81 +47,61 @@ public static async Task<int> Diff(

if (componentVersions)
{
result.ComponentVersions = new DiffItem<Component>();

// there is some complexity here, components could be included
// multiple times, with the same or different versions
result.ComponentVersions = new Dictionary<string, DiffItem<Component>>();

// make a copy of components that are still to be processed
var fromComponents = new List<Component>(fromBom.Components);
var toComponents = new List<Component>(toBom.Components);

// ditch component versions that are in both
foreach (var component in fromBom.Components)
// unchanged component versions
// loop over the toBom and fromBom Components list as we will be modifying the fromComponents list
foreach (var fromComponent in fromBom.Components)
{
if (toBom.Components.Count(c => c.Name == component.Name && c.Version == component.Version) > 0)
// if component version is in both SBOMs
if (toBom.Components.Count(toComponent =>
toComponent.Group == fromComponent.Group
&& toComponent.Name == fromComponent.Name
&& toComponent.Version == fromComponent.Version
) > 0)
{
fromComponents.RemoveAll(c => c.Name == component.Name && c.Version == component.Version);
toComponents.RemoveAll(c => c.Name == component.Name && c.Version == component.Version);
var componentIdentifier = $"{fromComponent.Group}:{fromComponent.Name}";

if (!result.ComponentVersions.ContainsKey(componentIdentifier))
{
result.ComponentVersions.Add(componentIdentifier, new DiffItem<Component>());
}

result.ComponentVersions[componentIdentifier].Unchanged.Add(fromComponent);

fromComponents.RemoveAll(c => c.Group == fromComponent.Group && c.Name == fromComponent.Name && c.Version == fromComponent.Version);
toComponents.RemoveAll(c => c.Group == fromComponent.Group && c.Name == fromComponent.Name && c.Version == fromComponent.Version);
}
}

// find obviously added components
// take a copy of toComponents as we are modifying it
// added component versions
foreach (var component in new List<Component>(toComponents))
{
if (fromComponents.Count(c => c.Name == component.Name) == 0)
var componentIdentifier = $"{component.Group}:{component.Name}";
if (!result.ComponentVersions.ContainsKey(componentIdentifier))
{
result.ComponentVersions.Added.Add(component);
toComponents.RemoveAll(c => c.Name == component.Name && c.Version == component.Version);
result.ComponentVersions.Add(componentIdentifier, new DiffItem<Component>());
}

result.ComponentVersions[componentIdentifier].Added.Add(component);
}

// find obviously removed components
// take a copy of fromComponents as we are modifying it
// removed components versions
foreach (var component in new List<Component>(fromComponents))
{
if (toComponents.Count(c => c.Name == component.Name) == 0)
var componentIdentifier = $"{component.Group}:{component.Name}";
if (!result.ComponentVersions.ContainsKey(componentIdentifier))
{
result.ComponentVersions.Removed.Add(component);
fromComponents.RemoveAll(c => c.Name == component.Name && c.Version == component.Version);
result.ComponentVersions.Add(componentIdentifier, new DiffItem<Component>());
}
}

// now we should have modified components left over
//
// but this situation is possible (or the reverse)
//
// From:
// Component A v1.0.0
//
// To:
// Component A v1.0.1
// Component A v1.0.2
//
// (╯°□°)╯︵ ┻━┻
//
// we'll treat the first match as modified, and any others as
// added or removed

foreach (var fromComponent in new List<Component>(fromComponents))
{
var toComponent = toComponents.FirstOrDefault(c => c.Name == fromComponent.Name);
if (toComponent != null)
{
result.ComponentVersions.Modified.Add(new ModifiedDiffItem<Component>
{
From = fromComponent,
To = toComponent
});

fromComponents.RemoveAll(c => c.Name == fromComponent.Name && c.Version == fromComponent.Version);
toComponents.RemoveAll(c => c.Name == toComponent.Name && c.Version == toComponent.Version);
}
result.ComponentVersions[componentIdentifier].Removed.Add(component);
}
// now everything left over we'll treat as added or removed
result.ComponentVersions.Removed.AddRange(fromComponents);
result.ComponentVersions.Added.AddRange(toComponents);

}

if (outputFormat == StandardOutputFormat.json)
Expand Down Expand Up @@ -151,33 +131,35 @@ public static async Task<int> Diff(
{
Console.WriteLine("Component versions that have changed:");
Console.WriteLine();
if (
result.ComponentVersions.Removed.Count == 0
&& result.ComponentVersions.Modified.Count == 0
&& result.ComponentVersions.Removed.Count == 0
)
{
Console.WriteLine("None");
}
else

var changes = false;
foreach (var entry in result.ComponentVersions)
{
foreach (var component in result.ComponentVersions.Added)
var componentDiffItem = entry.Value;
if (componentDiffItem.Added.Count > 0 || componentDiffItem.Removed.Count > 0)
{
Console.WriteLine($"+ {component.Name}@{component.Version}");
Console.WriteLine();
}
foreach (var component in result.ComponentVersions.Modified)
{
Console.WriteLine($"- {component.From.Name}@{component.From.Version}");
Console.WriteLine($"+ {component.To.Name}@{component.To.Version}");
Console.WriteLine();
}
foreach (var component in result.ComponentVersions.Removed)
{
Console.WriteLine($"- {component.Name}@{component.Version}");
changes = true;
foreach (var component in componentDiffItem.Removed)
{
Console.WriteLine($"- {component.Group} {component.Name} @ {component.Version}");
}
foreach (var component in componentDiffItem.Unchanged)
{
Console.WriteLine($"= {component.Group} {component.Name} @ {component.Version}");
}
foreach (var component in componentDiffItem.Added)
{
Console.WriteLine($"+ {component.Group} {component.Name} @ {component.Version}");
}
Console.WriteLine();
}
}

if (!changes)
{
Console.WriteLine("None");
}

Console.WriteLine();
}
}
Expand Down
11 changes: 3 additions & 8 deletions cyclonedx/Models/DiffResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,16 @@

namespace CycloneDX.CLI.Models
{
public class ModifiedDiffItem<T>
{
public T From { get; set; }
public T To { get; set; }
}

public class DiffItem<T>
{
public List<T> Added { get; set; } = new List<T>();
public List<ModifiedDiffItem<T>> Modified { get; set; } = new List<ModifiedDiffItem<T>>();
public List<T> Removed { get; set; } = new List<T>();
public List<T> Unchanged { get; set; } = new List<T>();
}

public class DiffResult
{
public DiffItem<Component> ComponentVersions { get; set; }
// default to nulls. A value, even if it is empty is to indicate that this option has been invoked.
public Dictionary<string,DiffItem<Component>> ComponentVersions { get; set; } = null;
}
}

0 comments on commit e1af5ca

Please sign in to comment.