From 73bc15d738322dcde8169c8a4bf9ada735348fb5 Mon Sep 17 00:00:00 2001 From: Omid Valipour Date: Sun, 5 May 2024 19:24:23 +0200 Subject: [PATCH 01/31] Added GitGraphProvider and Updated GraphProviderKind and GraphProviderFactory --- Assets/SEE/GraphProviders/GitGraphProvider.cs | 282 ++++++++++++++++++ .../GraphProviders/GitGraphProvider.cs.meta | 11 + .../GraphProviders/GraphProviderFactory.cs | 1 + .../SEE/GraphProviders/GraphProviderKind.cs | 6 +- 4 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 Assets/SEE/GraphProviders/GitGraphProvider.cs create mode 100644 Assets/SEE/GraphProviders/GitGraphProvider.cs.meta diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs new file mode 100644 index 0000000000..d618578379 --- /dev/null +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -0,0 +1,282 @@ +using Cysharp.Threading.Tasks; +using LibGit2Sharp; +using SEE.DataModel.DG; +using SEE.Game.City; +using SEE.Utils.Config; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace SEE.GraphProviders +{ + /// + /// Calculates metrics between 2 revisions from a git repository and adds these to a graph. + /// + [Serializable] + public class GitGraphProvider : GraphProvider + { + /// + /// The path to the VCS containing the two revisions to be compared. + /// + private string VCSPath = string.Empty; + + /// + /// The older revision that constitutes the baseline of the comparison. + /// + private string OldRevision = string.Empty; + + /// + /// The newer revision against which the is to be compared. + /// + private string NewRevision = string.Empty; + + /// + /// Calculates metrics between 2 revisions from a git repository and adds these to . + /// The resulting graph is returned. + /// + /// an existing graph where to add the metrics + /// this value is currently ignored + /// this value is currently ignored + /// this value is currently ignored + /// the input with metrics added + /// thrown in case + /// is undefined or does not exist or is null + /// thrown in case is + /// null; this is currently not supported. + public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, + Action changePercentage = null, + CancellationToken token = default) + { + CheckArguments(city); + if (graph == null) + { + throw new NotImplementedException(); + } + else + { + AddLineofCodeChurnMetric(graph, VCSPath, OldRevision, NewRevision); + AddNumberofDevelopersMetric(graph, VCSPath, OldRevision, NewRevision); + AddCommitFrequencyMetric(graph, VCSPath, OldRevision, NewRevision); + + return UniTask.FromResult(graph); + } + } + + public override GraphProviderKind GetKind() + { + return GraphProviderKind.Git; + } + + /// + /// Label of attribute in the configuration file. + /// + private const string vcsPathLabel = "VCSPath"; + + /// + /// Label of attribute in the configuration file. + /// + private const string oldRevisionLabel = "OldRevision"; + + /// + /// Label of attribute in the configuration file. + /// + private const string newRevisionLabel = "NewRevision"; + + protected override void SaveAttributes(ConfigWriter writer) + { + writer.Save(VCSPath, vcsPathLabel); + writer.Save(OldRevision, oldRevisionLabel); + writer.Save(NewRevision, newRevisionLabel); + } + + protected override void RestoreAttributes(Dictionary attributes) + { + ConfigIO.Restore(attributes, vcsPathLabel, ref VCSPath); + ConfigIO.Restore(attributes, oldRevisionLabel, ref OldRevision); + ConfigIO.Restore(attributes, newRevisionLabel, ref NewRevision); + } + + /// + /// Checks whether the assumptions on and + /// and and hold. + /// If not, exceptions are thrown accordingly. + /// + /// To be checked + /// thrown in case , + /// or or + /// is undefined or does not exist or is null or is not a DiffCity + protected void CheckArguments(AbstractSEECity city) + { + if (city == null) + { + throw new ArgumentException("The given city is null.\n"); + } + else + { + if (city is DiffCity diffcity) + { + OldRevision = diffcity.OldRevision; + NewRevision = diffcity.NewRevision; + VCSPath = diffcity.VCSPath.Path; + + if (string.IsNullOrEmpty(VCSPath)) + { + throw new ArgumentException("Empty VCS Path.\n"); + } + if (!Directory.Exists(VCSPath)) + { + throw new ArgumentException($"Directory {VCSPath} does not exist.\n"); + } + if (string.IsNullOrEmpty(OldRevision)) + { + throw new ArgumentException("Empty Old Revision.\n"); + } + if (string.IsNullOrEmpty(NewRevision)) + { + throw new ArgumentException("Empty New Revision.\n"); + } + } + else + { + throw new ArgumentException("To generate Git metrics, the given city should be a DiffCity.\n"); + } + } + } + + /// + /// Calculates the number of lines of code added and deleted for each file changed between two commits and adds them as metrics to . + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older revision that constitutes the baseline of the comparison + /// the newer revision against which the is to be compared + protected void AddLineofCodeChurnMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + { + using (var repo = new Repository(vcsPath)) + { + var oldCommit = repo.Lookup(oldRevision); + var newCommit = repo.Lookup(newRevision); + + var changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); + + foreach (var change in changes) + { + foreach (var node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == change.Path) + { + node.SetInt("Lines Added", change.LinesAdded); + node.SetInt("Lines Deleted", change.LinesDeleted); + } + } + } + } + } + + /// + /// Calculates the number of unique developers who contributed to each file for each file changed between two commits and adds it as a metric to . + /// + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older revision that constitutes the baseline of the comparison + /// the newer revision against which the is to be compared + protected void AddNumberofDevelopersMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + { + using (var repo = new Repository(vcsPath)) + { + var oldCommit = repo.Lookup(oldRevision); + var newCommit = repo.Lookup(newRevision); + + var commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); + + var uniqueContributorsPerFile = new Dictionary>(); + + foreach (var commit in commits) + { + if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) + { + foreach (var parent in commit.Parents) + { + var changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (var change in changes) + { + var filePath = change.Path; + var id = commit.Author.Email; + + if (!uniqueContributorsPerFile.ContainsKey(filePath)) + { + uniqueContributorsPerFile[filePath] = new HashSet(); + } + + uniqueContributorsPerFile[filePath].Add(id); + } + } + } + } + foreach (var entry in uniqueContributorsPerFile) + { + foreach (var node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == entry.Key) + { + node.SetInt("Number of Developers", entry.Value.Count); + } + } + } + } + } + + /// + /// Calculates the number of times each file was changed for each file changed between two commits and adds it as a metric to . + /// + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older revision that constitutes the baseline of the comparison + /// the newer revision against which the is to be compared + protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + { + using (var repo = new Repository(vcsPath)) + { + var oldCommit = repo.Lookup(oldRevision); + var newCommit = repo.Lookup(newRevision); + + var commitsBetween = repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = newCommit, + ExcludeReachableFrom = oldCommit + }); + + Dictionary fileCommitCounts = new Dictionary(); + + foreach (var commit in commitsBetween) + { + foreach (var parent in commit.Parents) + { + var changes = repo.Diff.Compare(parent.Tree, commit.Tree); + foreach (var change in changes) + { + var filePath = change.Path; + if (fileCommitCounts.ContainsKey(filePath)) + fileCommitCounts[filePath]++; + else + fileCommitCounts.Add(filePath, 1); + } + } + } + + foreach (var entry in fileCommitCounts.OrderByDescending(x => x.Value)) + { + foreach (var node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == entry.Key) + { + node.SetInt("Commit Frequency", entry.Value); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs.meta b/Assets/SEE/GraphProviders/GitGraphProvider.cs.meta new file mode 100644 index 0000000000..96e900bb6e --- /dev/null +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 508dcb5d796d13c49ac65298ee6f24a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SEE/GraphProviders/GraphProviderFactory.cs b/Assets/SEE/GraphProviders/GraphProviderFactory.cs index 701243d204..e55db4f5ed 100644 --- a/Assets/SEE/GraphProviders/GraphProviderFactory.cs +++ b/Assets/SEE/GraphProviders/GraphProviderFactory.cs @@ -28,6 +28,7 @@ internal static GraphProvider NewInstance(GraphProviderKind kind) GraphProviderKind.JaCoCo => new JaCoCoGraphProvider(), GraphProviderKind.MergeDiff => new MergeDiffGraphProvider(), GraphProviderKind.LSP => new LSPGraphProvider(), + GraphProviderKind.Git => new GitGraphProvider(), _ => throw new NotImplementedException($"Not implemented for {kind}") }; } diff --git a/Assets/SEE/GraphProviders/GraphProviderKind.cs b/Assets/SEE/GraphProviders/GraphProviderKind.cs index 2e48565a47..38a3e452b5 100644 --- a/Assets/SEE/GraphProviders/GraphProviderKind.cs +++ b/Assets/SEE/GraphProviders/GraphProviderKind.cs @@ -40,6 +40,10 @@ public enum GraphProviderKind /// /// For . /// - LSP + LSP, + /// + /// For . + /// + Git } } From 9da9a706a189b2094483a55db1740d630c44b1fe Mon Sep 17 00:00:00 2001 From: Omid Valipour Date: Sat, 11 May 2024 17:56:56 +0200 Subject: [PATCH 02/31] Applied changes requested by reviewers --- Assets/SEE/DataModel/DG/StandardNames.cs | 25 +++- Assets/SEE/GraphProviders/GitGraphProvider.cs | 108 +++++++++--------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/Assets/SEE/DataModel/DG/StandardNames.cs b/Assets/SEE/DataModel/DG/StandardNames.cs index 9a065c063a..5f6bbafdbb 100644 --- a/Assets/SEE/DataModel/DG/StandardNames.cs +++ b/Assets/SEE/DataModel/DG/StandardNames.cs @@ -190,4 +190,27 @@ public static class ChangeMarkers /// public const string IsChanged = "Change.IsChanged"; } -} + + /// + /// Defines names of node attributes for Git metrics. + /// + public static class Git + { + /// + /// The number of lines of code added for a file that was changed between two commits. + /// + public const string LinesAdded = "Metric.Git.Lines_Added"; + /// + /// The number of lines of code deleted for a file that was changed between two commits. + /// + public const string LinesDeleted = "Metric.Git.Lines_Deleted"; + /// + /// The number of unique developers who contributed to a file that was changed between two commits. + /// + public const string NumberOfDevelopers = "Metric.Git.Number_of_Developers"; + /// + /// The number of times a file was changed between two commits. + /// + public const string CommitFrequency = "Metric.Git.Commit_Frequency"; + } +} \ No newline at end of file diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs index d618578379..c3b4dca84e 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -12,7 +12,7 @@ namespace SEE.GraphProviders { /// - /// Calculates metrics between 2 revisions from a git repository and adds these to a graph. + /// Calculates metrics between two revisions from a git repository and adds these to a graph. /// [Serializable] public class GitGraphProvider : GraphProvider @@ -33,7 +33,7 @@ public class GitGraphProvider : GraphProvider private string NewRevision = string.Empty; /// - /// Calculates metrics between 2 revisions from a git repository and adds these to . + /// Calculates metrics between two revisions from a git repository and adds these to . /// The resulting graph is returned. /// /// an existing graph where to add the metrics @@ -56,10 +56,15 @@ public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, } else { - AddLineofCodeChurnMetric(graph, VCSPath, OldRevision, NewRevision); - AddNumberofDevelopersMetric(graph, VCSPath, OldRevision, NewRevision); - AddCommitFrequencyMetric(graph, VCSPath, OldRevision, NewRevision); + using (Repository repo = new(VCSPath)) + { + Commit OldCommit = repo.Lookup(OldRevision); + Commit NewCommit = repo.Lookup(NewRevision); + AddLineofCodeChurnMetric(graph, VCSPath, OldCommit, NewCommit); + AddNumberofDevelopersMetric(graph, VCSPath, OldCommit, NewCommit); + AddCommitFrequencyMetric(graph, VCSPath, OldCommit, NewCommit); + } return UniTask.FromResult(graph); } } @@ -149,25 +154,22 @@ protected void CheckArguments(AbstractSEECity city) /// Calculates the number of lines of code added and deleted for each file changed between two commits and adds them as metrics to . /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared - /// the older revision that constitutes the baseline of the comparison - /// the newer revision against which the is to be compared - protected void AddLineofCodeChurnMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is to be compared + protected void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (var repo = new Repository(vcsPath)) + using (Repository repo = new(vcsPath)) { - var oldCommit = repo.Lookup(oldRevision); - var newCommit = repo.Lookup(newRevision); + Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); - var changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); - - foreach (var change in changes) + foreach (PatchEntryChanges change in changes) { - foreach (var node in graph.Nodes()) + foreach (Node node in graph.Nodes()) { if (node.ID.Replace('\\', '/') == change.Path) { - node.SetInt("Lines Added", change.LinesAdded); - node.SetInt("Lines Deleted", change.LinesDeleted); + node.SetInt(Git.LinesAdded, change.LinesAdded); + node.SetInt(Git.LinesDeleted, change.LinesDeleted); } } } @@ -179,49 +181,46 @@ protected void AddLineofCodeChurnMetric(Graph graph, string vcsPath, string oldR /// /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared - /// the older revision that constitutes the baseline of the comparison - /// the newer revision against which the is to be compared - protected void AddNumberofDevelopersMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is to be compared + protected void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (var repo = new Repository(vcsPath)) + using (Repository repo = new(vcsPath)) { - var oldCommit = repo.Lookup(oldRevision); - var newCommit = repo.Lookup(newRevision); - - var commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); + ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); - var uniqueContributorsPerFile = new Dictionary>(); + Dictionary> uniqueContributorsPerFile = new(); - foreach (var commit in commits) + foreach (Commit commit in commits) { if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) { - foreach (var parent in commit.Parents) + foreach (Commit parent in commit.Parents) { - var changes = repo.Diff.Compare(parent.Tree, commit.Tree); + Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); - foreach (var change in changes) + foreach (PatchEntryChanges change in changes) { - var filePath = change.Path; - var id = commit.Author.Email; + string filePath = change.Path; + string id = commit.Author.Email; if (!uniqueContributorsPerFile.ContainsKey(filePath)) { uniqueContributorsPerFile[filePath] = new HashSet(); } - uniqueContributorsPerFile[filePath].Add(id); } } } } - foreach (var entry in uniqueContributorsPerFile) + + foreach (KeyValuePair> entry in uniqueContributorsPerFile) { - foreach (var node in graph.Nodes()) + foreach (Node node in graph.Nodes()) { if (node.ID.Replace('\\', '/') == entry.Key) { - node.SetInt("Number of Developers", entry.Value.Count); + node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); } } } @@ -233,46 +232,49 @@ protected void AddNumberofDevelopersMetric(Graph graph, string vcsPath, string o /// /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared - /// the older revision that constitutes the baseline of the comparison - /// the newer revision against which the is to be compared - protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, string oldRevision, string newRevision) + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is to be compared + protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (var repo = new Repository(vcsPath)) + using (Repository repo = new(vcsPath)) { - var oldCommit = repo.Lookup(oldRevision); - var newCommit = repo.Lookup(newRevision); - - var commitsBetween = repo.Commits.QueryBy(new CommitFilter + ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = newCommit, ExcludeReachableFrom = oldCommit }); - Dictionary fileCommitCounts = new Dictionary(); + Dictionary fileCommitCounts = new(); - foreach (var commit in commitsBetween) + foreach (Commit commit in commitsBetween) { - foreach (var parent in commit.Parents) + foreach (Commit parent in commit.Parents) { - var changes = repo.Diff.Compare(parent.Tree, commit.Tree); - foreach (var change in changes) + TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (TreeEntryChanges change in changes) { - var filePath = change.Path; + string filePath = change.Path; + if (fileCommitCounts.ContainsKey(filePath)) + { fileCommitCounts[filePath]++; + } else + { fileCommitCounts.Add(filePath, 1); + } } } } - foreach (var entry in fileCommitCounts.OrderByDescending(x => x.Value)) + foreach (KeyValuePair entry in fileCommitCounts.OrderByDescending(x => x.Value)) { - foreach (var node in graph.Nodes()) + foreach (Node node in graph.Nodes()) { if (node.ID.Replace('\\', '/') == entry.Key) { - node.SetInt("Commit Frequency", entry.Value); + node.SetInt(Git.CommitFrequency, entry.Value); } } } From 7d9745c6d917405c3c17d9b0de2bda42f0305de8 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Sun, 19 May 2024 19:32:46 +0200 Subject: [PATCH 03/31] Use nameof. --- Assets/SEE/GraphProviders/GitGraphProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs index c3b4dca84e..fdc8ed1f34 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -145,7 +145,7 @@ protected void CheckArguments(AbstractSEECity city) } else { - throw new ArgumentException("To generate Git metrics, the given city should be a DiffCity.\n"); + throw new ArgumentException($"To generate Git metrics, the given city should be a {nameof(DiffCity)}.\n"); } } } @@ -281,4 +281,4 @@ protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldC } } } -} \ No newline at end of file +} From 7298bba72fb8f03899e8b43fee41c87856c93dbf Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Sun, 19 May 2024 19:37:58 +0200 Subject: [PATCH 04/31] Capital letter for 'of' in metric name. This is consistent with the naming of other metrics. --- Assets/SEE/DataModel/DG/StandardNames.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/SEE/DataModel/DG/StandardNames.cs b/Assets/SEE/DataModel/DG/StandardNames.cs index 5f6bbafdbb..ce51c50a8f 100644 --- a/Assets/SEE/DataModel/DG/StandardNames.cs +++ b/Assets/SEE/DataModel/DG/StandardNames.cs @@ -207,10 +207,10 @@ public static class Git /// /// The number of unique developers who contributed to a file that was changed between two commits. /// - public const string NumberOfDevelopers = "Metric.Git.Number_of_Developers"; + public const string NumberOfDevelopers = "Metric.Git.Number_Of_Developers"; /// /// The number of times a file was changed between two commits. /// public const string CommitFrequency = "Metric.Git.Commit_Frequency"; } -} \ No newline at end of file +} From a33a817b80d49a86ac2a455c98f425240d943ea4 Mon Sep 17 00:00:00 2001 From: Omid Valipour Date: Wed, 29 May 2024 14:26:10 +0200 Subject: [PATCH 05/31] Added test cases for GitGraphProvider --- Assets/SEETests/TestGraphProviders.cs | 48 ++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index be74313d69..9e25acf626 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -2,7 +2,9 @@ using NUnit.Framework; using SEE.DataModel.DG; using SEE.Game.City; +using SEE.Utils.Paths; using System.Collections; +using System.IO; using UnityEngine; using UnityEngine.TestTools; @@ -122,5 +124,49 @@ public IEnumerator TestMergeDiffGraphProvider() => } } }); + + [UnityTest] + public IEnumerator TestGitGraphProvider() => + UniTask.ToCoroutine(async () => + { + LogAssert.ignoreFailingMessages = true; + + GameObject go = new(); + + DiffCity city = go.AddComponent(); + city.VCSPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)); + city.OldRevision = "887e1fc1d6fe87ee1178822b5eeb666e62af3710"; + city.NewRevision = "5efa95913a6e894e5340f07fab26c9958b5c1096"; + + PipelineGraphProvider pipeline = new(); + + { + GraphProvider provider = new VCSGraphProvider() + { + RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), + CommitID = "5efa95913a6e894e5340f07fab26c9958b5c1096", + }; + pipeline.Add(provider); + } + + { + GraphProvider provider = new GitGraphProvider() + { + }; + pipeline.Add(provider); + } + + Graph loaded = await pipeline.ProvideAsync(new Graph(""), city); + Assert.IsNotNull(loaded); + Assert.IsTrue(loaded.NodeCount > 0); + + Assert.IsTrue(loaded.TryGetNode("Assets/SEE/GraphProviders/VCSGraphProvider.cs", out Node node)); + + // Metric from Git. + { + Assert.IsTrue(node.TryGetInt(Git.LinesAdded, out int value)); + Assert.AreEqual(40, value); + } + }); } -} +} \ No newline at end of file From 45ca5936ce508703a77ff3d50664fa31e4b58641 Mon Sep 17 00:00:00 2001 From: Omid Valipour Date: Fri, 7 Jun 2024 10:13:47 +0200 Subject: [PATCH 06/31] Made a small change to TestGitGraphProvider --- Assets/SEETests/TestGraphProviders.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index 40921191e8..3d48c0d6ec 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -129,10 +129,7 @@ public IEnumerator TestMergeDiffGraphProvider() => public IEnumerator TestGitGraphProvider() => UniTask.ToCoroutine(async () => { - LogAssert.ignoreFailingMessages = true; - GameObject go = new(); - DiffCity city = go.AddComponent(); city.VCSPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)); city.OldRevision = "887e1fc1d6fe87ee1178822b5eeb666e62af3710"; From 835110d463280391557c0eedca379293bc702525 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 15:48:07 +0200 Subject: [PATCH 07/31] #723 Shortened unnecessary fully qualified name. --- Assets/SEETests/TestGraphProviders.cs | 36 ++++++++------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index 40921191e8..e4318c4054 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -20,7 +20,7 @@ public IEnumerator TestGXLGraphProvider() => UniTask.ToCoroutine(async () => { GraphProvider provider = new GXLGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.gxl.xz") }; + { Path = new FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.gxl.xz") }; GameObject go = new(); SEECity city = go.AddComponent(); @@ -42,18 +42,18 @@ public IEnumerator TestCSVJaCoCoGXLGraphProvider() => { GraphProvider provider = new GXLGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.gxl.xz") }; + { Path = new FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.gxl.xz") }; pipeline.Add(provider); } { GraphProvider provider = new JaCoCoGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/JLGExample/jacoco.xml") }; + { Path = new FilePath(Application.streamingAssetsPath + "/JLGExample/jacoco.xml") }; pipeline.Add(provider); } { GraphProvider provider = new CSVGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.csv") }; + { Path = new FilePath(Application.streamingAssetsPath + "/JLGExample/CodeFacts.csv") }; pipeline.Add(provider); } @@ -87,14 +87,14 @@ public IEnumerator TestMergeDiffGraphProvider() => Graph graph; { GraphProvider provider = new GXLGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/mini-evolution/CodeFacts-5.gxl") }; + { Path = new FilePath(Application.streamingAssetsPath + "/mini-evolution/CodeFacts-5.gxl") }; graph = await provider.ProvideAsync(new Graph(""), city); } { // Older graph GraphProvider provider = new GXLGraphProvider() - { Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/mini-evolution/CodeFacts-1.gxl") }; + { Path = new FilePath(Application.streamingAssetsPath + "/mini-evolution/CodeFacts-1.gxl") }; MergeDiffGraphProvider mergeDiffProvider = new() { @@ -129,7 +129,7 @@ public IEnumerator TestMergeDiffGraphProvider() => public IEnumerator TestGitGraphProvider() => UniTask.ToCoroutine(async () => { - LogAssert.ignoreFailingMessages = true; + //LogAssert.ignoreFailingMessages = true; GameObject go = new(); @@ -138,25 +138,9 @@ public IEnumerator TestGitGraphProvider() => city.OldRevision = "887e1fc1d6fe87ee1178822b5eeb666e62af3710"; city.NewRevision = "5efa95913a6e894e5340f07fab26c9958b5c1096"; - PipelineGraphProvider pipeline = new(); + GraphProvider provider = new GitGraphProvider(); - { - GraphProvider provider = new VCSGraphProvider() - { - RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), - CommitID = "5efa95913a6e894e5340f07fab26c9958b5c1096", - }; - pipeline.Add(provider); - } - - { - GraphProvider provider = new GitGraphProvider() - { - }; - pipeline.Add(provider); - } - - Graph loaded = await pipeline.ProvideAsync(new Graph(""), city); + Graph loaded = await provider.ProvideAsync(new Graph(""), city); Assert.IsNotNull(loaded); Assert.IsTrue(loaded.NodeCount > 0); @@ -169,4 +153,4 @@ public IEnumerator TestGitGraphProvider() => } }); } -} \ No newline at end of file +} From fb78316c1b5f4d850b797c14145d44c883b257ee Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 15:59:30 +0200 Subject: [PATCH 08/31] #723 Config IO stuff is now nested in a region at the bottom of the file. --- Assets/SEE/GraphProviders/GitGraphProvider.cs | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs index fdc8ed1f34..d25701689a 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -74,35 +74,6 @@ public override GraphProviderKind GetKind() return GraphProviderKind.Git; } - /// - /// Label of attribute in the configuration file. - /// - private const string vcsPathLabel = "VCSPath"; - - /// - /// Label of attribute in the configuration file. - /// - private const string oldRevisionLabel = "OldRevision"; - - /// - /// Label of attribute in the configuration file. - /// - private const string newRevisionLabel = "NewRevision"; - - protected override void SaveAttributes(ConfigWriter writer) - { - writer.Save(VCSPath, vcsPathLabel); - writer.Save(OldRevision, oldRevisionLabel); - writer.Save(NewRevision, newRevisionLabel); - } - - protected override void RestoreAttributes(Dictionary attributes) - { - ConfigIO.Restore(attributes, vcsPathLabel, ref VCSPath); - ConfigIO.Restore(attributes, oldRevisionLabel, ref OldRevision); - ConfigIO.Restore(attributes, newRevisionLabel, ref NewRevision); - } - /// /// Checks whether the assumptions on and /// and and hold. @@ -280,5 +251,38 @@ protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldC } } } + + #region ConfigIO + + /// + /// Label of attribute in the configuration file. + /// + private const string vcsPathLabel = "VCSPath"; + + /// + /// Label of attribute in the configuration file. + /// + private const string oldRevisionLabel = "OldRevision"; + + /// + /// Label of attribute in the configuration file. + /// + private const string newRevisionLabel = "NewRevision"; + + protected override void SaveAttributes(ConfigWriter writer) + { + writer.Save(VCSPath, vcsPathLabel); + writer.Save(OldRevision, oldRevisionLabel); + writer.Save(NewRevision, newRevisionLabel); + } + + protected override void RestoreAttributes(Dictionary attributes) + { + ConfigIO.Restore(attributes, vcsPathLabel, ref VCSPath); + ConfigIO.Restore(attributes, oldRevisionLabel, ref OldRevision); + ConfigIO.Restore(attributes, newRevisionLabel, ref NewRevision); + } + + #endregion } } From d4a8acb9d1894b7106d276bd70d2babc474368f4 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 16:07:41 +0200 Subject: [PATCH 09/31] #723 Fixed parameter reference. Made methods static. Simplified using. --- Assets/SEE/GraphProviders/GitGraphProvider.cs | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs index d25701689a..5930cfb700 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -122,131 +122,131 @@ protected void CheckArguments(AbstractSEECity city) } /// - /// Calculates the number of lines of code added and deleted for each file changed between two commits and adds them as metrics to . + /// Calculates the number of lines of code added and deleted for each file changed + /// between two commits and adds them as metrics to . /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is to be compared - protected void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + /// the newer commit against which the is + /// to be compared + protected static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (Repository repo = new(vcsPath)) - { - Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); + using Repository repo = new(vcsPath); + Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); - foreach (PatchEntryChanges change in changes) + foreach (PatchEntryChanges change in changes) + { + foreach (Node node in graph.Nodes()) { - foreach (Node node in graph.Nodes()) + if (node.ID.Replace('\\', '/') == change.Path) { - if (node.ID.Replace('\\', '/') == change.Path) - { - node.SetInt(Git.LinesAdded, change.LinesAdded); - node.SetInt(Git.LinesDeleted, change.LinesDeleted); - } + node.SetInt(Git.LinesAdded, change.LinesAdded); + node.SetInt(Git.LinesDeleted, change.LinesDeleted); } } } } /// - /// Calculates the number of unique developers who contributed to each file for each file changed between two commits and adds it as a metric to . + /// Calculates the number of unique developers who contributed to each file for each file changed + /// between two commits and adds it as a metric to . /// /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is to be compared - protected void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + /// the newer commit against which the is + /// to be compared + protected static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (Repository repo = new(vcsPath)) - { - ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); + using Repository repo = new(vcsPath); + ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); - Dictionary> uniqueContributorsPerFile = new(); + Dictionary> uniqueContributorsPerFile = new(); - foreach (Commit commit in commits) + foreach (Commit commit in commits) + { + if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) { - if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) + foreach (Commit parent in commit.Parents) { - foreach (Commit parent in commit.Parents) + Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (PatchEntryChanges change in changes) { - Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); + string filePath = change.Path; + string id = commit.Author.Email; - foreach (PatchEntryChanges change in changes) + if (!uniqueContributorsPerFile.ContainsKey(filePath)) { - string filePath = change.Path; - string id = commit.Author.Email; - - if (!uniqueContributorsPerFile.ContainsKey(filePath)) - { - uniqueContributorsPerFile[filePath] = new HashSet(); - } - uniqueContributorsPerFile[filePath].Add(id); + uniqueContributorsPerFile[filePath] = new HashSet(); } + uniqueContributorsPerFile[filePath].Add(id); } } } + } - foreach (KeyValuePair> entry in uniqueContributorsPerFile) + foreach (KeyValuePair> entry in uniqueContributorsPerFile) + { + foreach (Node node in graph.Nodes()) { - foreach (Node node in graph.Nodes()) + if (node.ID.Replace('\\', '/') == entry.Key) { - if (node.ID.Replace('\\', '/') == entry.Key) - { - node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); - } + node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); } } } } /// - /// Calculates the number of times each file was changed for each file changed between two commits and adds it as a metric to . + /// Calculates the number of times each file was changed for each file changed between + /// two commits and adds it as a metric to . /// /// an existing graph where to add the metrics /// the path to the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is to be compared - protected void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + /// the newer commit against which the is + /// to be compared + protected static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { - using (Repository repo = new(vcsPath)) + using Repository repo = new(vcsPath); + ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter { - ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter - { - IncludeReachableFrom = newCommit, - ExcludeReachableFrom = oldCommit - }); + IncludeReachableFrom = newCommit, + ExcludeReachableFrom = oldCommit + }); - Dictionary fileCommitCounts = new(); + Dictionary fileCommitCounts = new(); - foreach (Commit commit in commitsBetween) + foreach (Commit commit in commitsBetween) + { + foreach (Commit parent in commit.Parents) { - foreach (Commit parent in commit.Parents) + TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (TreeEntryChanges change in changes) { - TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); + string filePath = change.Path; - foreach (TreeEntryChanges change in changes) + if (fileCommitCounts.ContainsKey(filePath)) { - string filePath = change.Path; - - if (fileCommitCounts.ContainsKey(filePath)) - { - fileCommitCounts[filePath]++; - } - else - { - fileCommitCounts.Add(filePath, 1); - } + fileCommitCounts[filePath]++; + } + else + { + fileCommitCounts.Add(filePath, 1); } } } + } - foreach (KeyValuePair entry in fileCommitCounts.OrderByDescending(x => x.Value)) + foreach (KeyValuePair entry in fileCommitCounts.OrderByDescending(x => x.Value)) + { + foreach (Node node in graph.Nodes()) { - foreach (Node node in graph.Nodes()) + if (node.ID.Replace('\\', '/') == entry.Key) { - if (node.ID.Replace('\\', '/') == entry.Key) - { - node.SetInt(Git.CommitFrequency, entry.Value); - } + node.SetInt(Git.CommitFrequency, entry.Value); } } } From 44a67200912fb7910e9d16329ed9e781703a229c Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 16:15:33 +0200 Subject: [PATCH 10/31] #723 Moved metric methods to a separate class. It may be useful for VCSGraphProvider, too. --- Assets/SEE/GraphProviders/GitGraphProvider.cs | 138 +---------------- Assets/SEE/GraphProviders/VCSMetrics.cs | 145 ++++++++++++++++++ Assets/SEE/GraphProviders/VCSMetrics.cs.meta | 11 ++ 3 files changed, 159 insertions(+), 135 deletions(-) create mode 100644 Assets/SEE/GraphProviders/VCSMetrics.cs create mode 100644 Assets/SEE/GraphProviders/VCSMetrics.cs.meta diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/GitGraphProvider.cs index 5930cfb700..d8024c93cd 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/GitGraphProvider.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; namespace SEE.GraphProviders @@ -61,9 +60,9 @@ public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, Commit OldCommit = repo.Lookup(OldRevision); Commit NewCommit = repo.Lookup(NewRevision); - AddLineofCodeChurnMetric(graph, VCSPath, OldCommit, NewCommit); - AddNumberofDevelopersMetric(graph, VCSPath, OldCommit, NewCommit); - AddCommitFrequencyMetric(graph, VCSPath, OldCommit, NewCommit); + VCSMetrics.AddLineofCodeChurnMetric(graph, VCSPath, OldCommit, NewCommit); + VCSMetrics.AddNumberofDevelopersMetric(graph, VCSPath, OldCommit, NewCommit); + VCSMetrics.AddCommitFrequencyMetric(graph, VCSPath, OldCommit, NewCommit); } return UniTask.FromResult(graph); } @@ -121,137 +120,6 @@ protected void CheckArguments(AbstractSEECity city) } } - /// - /// Calculates the number of lines of code added and deleted for each file changed - /// between two commits and adds them as metrics to . - /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared - /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is - /// to be compared - protected static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) - { - using Repository repo = new(vcsPath); - Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); - - foreach (PatchEntryChanges change in changes) - { - foreach (Node node in graph.Nodes()) - { - if (node.ID.Replace('\\', '/') == change.Path) - { - node.SetInt(Git.LinesAdded, change.LinesAdded); - node.SetInt(Git.LinesDeleted, change.LinesDeleted); - } - } - } - } - - /// - /// Calculates the number of unique developers who contributed to each file for each file changed - /// between two commits and adds it as a metric to . - /// - /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared - /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is - /// to be compared - protected static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) - { - using Repository repo = new(vcsPath); - ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); - - Dictionary> uniqueContributorsPerFile = new(); - - foreach (Commit commit in commits) - { - if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) - { - foreach (Commit parent in commit.Parents) - { - Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); - - foreach (PatchEntryChanges change in changes) - { - string filePath = change.Path; - string id = commit.Author.Email; - - if (!uniqueContributorsPerFile.ContainsKey(filePath)) - { - uniqueContributorsPerFile[filePath] = new HashSet(); - } - uniqueContributorsPerFile[filePath].Add(id); - } - } - } - } - - foreach (KeyValuePair> entry in uniqueContributorsPerFile) - { - foreach (Node node in graph.Nodes()) - { - if (node.ID.Replace('\\', '/') == entry.Key) - { - node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); - } - } - } - } - - /// - /// Calculates the number of times each file was changed for each file changed between - /// two commits and adds it as a metric to . - /// - /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared - /// the older commit that constitutes the baseline of the comparison - /// the newer commit against which the is - /// to be compared - protected static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) - { - using Repository repo = new(vcsPath); - ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter - { - IncludeReachableFrom = newCommit, - ExcludeReachableFrom = oldCommit - }); - - Dictionary fileCommitCounts = new(); - - foreach (Commit commit in commitsBetween) - { - foreach (Commit parent in commit.Parents) - { - TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); - - foreach (TreeEntryChanges change in changes) - { - string filePath = change.Path; - - if (fileCommitCounts.ContainsKey(filePath)) - { - fileCommitCounts[filePath]++; - } - else - { - fileCommitCounts.Add(filePath, 1); - } - } - } - } - - foreach (KeyValuePair entry in fileCommitCounts.OrderByDescending(x => x.Value)) - { - foreach (Node node in graph.Nodes()) - { - if (node.ID.Replace('\\', '/') == entry.Key) - { - node.SetInt(Git.CommitFrequency, entry.Value); - } - } - } - } - #region ConfigIO /// diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs new file mode 100644 index 0000000000..8bd2c673a5 --- /dev/null +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -0,0 +1,145 @@ +using LibGit2Sharp; +using SEE.DataModel.DG; +using System.Collections.Generic; +using System.Linq; + +namespace SEE.GraphProviders +{ + /// + /// Calculates metrics between two revisions from a version control system + /// and adds these to a graph. + /// + public static class VCSMetrics + { + /// + /// Calculates the number of lines of code added and deleted for each file changed + /// between two commits and adds them as metrics to . + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is + /// to be compared + public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + { + using Repository repo = new(vcsPath); + Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); + + foreach (PatchEntryChanges change in changes) + { + foreach (Node node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == change.Path) + { + node.SetInt(Git.LinesAdded, change.LinesAdded); + node.SetInt(Git.LinesDeleted, change.LinesDeleted); + } + } + } + } + + /// + /// Calculates the number of unique developers who contributed to each file for each file changed + /// between two commits and adds it as a metric to . + /// + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is + /// to be compared + public static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + { + using Repository repo = new(vcsPath); + ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); + + Dictionary> uniqueContributorsPerFile = new(); + + foreach (Commit commit in commits) + { + if (commit.Author.When >= oldCommit.Author.When && commit.Author.When <= newCommit.Author.When) + { + foreach (Commit parent in commit.Parents) + { + Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (PatchEntryChanges change in changes) + { + string filePath = change.Path; + string id = commit.Author.Email; + + if (!uniqueContributorsPerFile.ContainsKey(filePath)) + { + uniqueContributorsPerFile[filePath] = new HashSet(); + } + uniqueContributorsPerFile[filePath].Add(id); + } + } + } + } + + foreach (KeyValuePair> entry in uniqueContributorsPerFile) + { + foreach (Node node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == entry.Key) + { + node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); + } + } + } + } + + /// + /// Calculates the number of times each file was changed for each file changed between + /// two commits and adds it as a metric to . + /// + /// an existing graph where to add the metrics + /// the path to the VCS containing the two revisions to be compared + /// the older commit that constitutes the baseline of the comparison + /// the newer commit against which the is + /// to be compared + public static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + { + using Repository repo = new(vcsPath); + ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = newCommit, + ExcludeReachableFrom = oldCommit + }); + + Dictionary fileCommitCounts = new(); + + foreach (Commit commit in commitsBetween) + { + foreach (Commit parent in commit.Parents) + { + TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); + + foreach (TreeEntryChanges change in changes) + { + string filePath = change.Path; + + if (fileCommitCounts.ContainsKey(filePath)) + { + fileCommitCounts[filePath]++; + } + else + { + fileCommitCounts.Add(filePath, 1); + } + } + } + } + + foreach (KeyValuePair entry in fileCommitCounts.OrderByDescending(x => x.Value)) + { + foreach (Node node in graph.Nodes()) + { + if (node.ID.Replace('\\', '/') == entry.Key) + { + node.SetInt(Git.CommitFrequency, entry.Value); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs.meta b/Assets/SEE/GraphProviders/VCSMetrics.cs.meta new file mode 100644 index 0000000000..73717e9386 --- /dev/null +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 850b92bac9f9bcb4fb113dd317959363 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 832269bc29253941c762daf2102634409118a7b1 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 16:18:24 +0200 Subject: [PATCH 11/31] #723 Renamed GitGraphProvider to VCSMetricsProvider. The new name better reflects what this class is doing. --- Assets/SEE/GraphProviders/GraphProviderFactory.cs | 2 +- Assets/SEE/GraphProviders/GraphProviderKind.cs | 2 +- .../{GitGraphProvider.cs => VCSMetricsProvider.cs} | 2 +- .../{GitGraphProvider.cs.meta => VCSMetricsProvider.cs.meta} | 0 Assets/SEETests/TestGraphProviders.cs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename Assets/SEE/GraphProviders/{GitGraphProvider.cs => VCSMetricsProvider.cs} (99%) rename Assets/SEE/GraphProviders/{GitGraphProvider.cs.meta => VCSMetricsProvider.cs.meta} (100%) diff --git a/Assets/SEE/GraphProviders/GraphProviderFactory.cs b/Assets/SEE/GraphProviders/GraphProviderFactory.cs index d8ada3bccf..c18617d315 100644 --- a/Assets/SEE/GraphProviders/GraphProviderFactory.cs +++ b/Assets/SEE/GraphProviders/GraphProviderFactory.cs @@ -29,7 +29,7 @@ internal static GraphProvider NewInstance(GraphProviderKind kind) GraphProviderKind.MergeDiff => new MergeDiffGraphProvider(), GraphProviderKind.VCS => new VCSGraphProvider(), GraphProviderKind.LSP => new LSPGraphProvider(), - GraphProviderKind.Git => new GitGraphProvider(), + GraphProviderKind.Git => new VCSMetricsProvider(), _ => throw new NotImplementedException($"Not implemented for {kind}") }; } diff --git a/Assets/SEE/GraphProviders/GraphProviderKind.cs b/Assets/SEE/GraphProviders/GraphProviderKind.cs index d6ca54b57b..5ef771ceef 100644 --- a/Assets/SEE/GraphProviders/GraphProviderKind.cs +++ b/Assets/SEE/GraphProviders/GraphProviderKind.cs @@ -46,7 +46,7 @@ public enum GraphProviderKind /// LSP, /// - /// For . + /// For . /// Git } diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs similarity index 99% rename from Assets/SEE/GraphProviders/GitGraphProvider.cs rename to Assets/SEE/GraphProviders/VCSMetricsProvider.cs index d8024c93cd..8664ef63dc 100644 --- a/Assets/SEE/GraphProviders/GitGraphProvider.cs +++ b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs @@ -14,7 +14,7 @@ namespace SEE.GraphProviders /// Calculates metrics between two revisions from a git repository and adds these to a graph. /// [Serializable] - public class GitGraphProvider : GraphProvider + public class VCSMetricsProvider : GraphProvider { /// /// The path to the VCS containing the two revisions to be compared. diff --git a/Assets/SEE/GraphProviders/GitGraphProvider.cs.meta b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta similarity index 100% rename from Assets/SEE/GraphProviders/GitGraphProvider.cs.meta rename to Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index d3e9e77775..b6d96fb8a6 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -135,7 +135,7 @@ public IEnumerator TestGitGraphProvider() => city.OldRevision = "887e1fc1d6fe87ee1178822b5eeb666e62af3710"; city.NewRevision = "5efa95913a6e894e5340f07fab26c9958b5c1096"; - GraphProvider provider = new GitGraphProvider(); + GraphProvider provider = new VCSMetricsProvider(); Graph loaded = await provider.ProvideAsync(new Graph(""), city); Assert.IsNotNull(loaded); From 5843eab28ba02a7d445ba0091e8fd651dc512b9e Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 7 Jun 2024 16:24:19 +0200 Subject: [PATCH 12/31] #723 Renamed GraphProviderKind.Git to GraphProviderKind.VCSMetrics. The former was too general and could be confused with VCS. --- Assets/SEE/GraphProviders/GraphProviderFactory.cs | 2 +- Assets/SEE/GraphProviders/GraphProviderKind.cs | 2 +- Assets/SEE/GraphProviders/VCSMetricsProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/SEE/GraphProviders/GraphProviderFactory.cs b/Assets/SEE/GraphProviders/GraphProviderFactory.cs index c18617d315..8e2deedc45 100644 --- a/Assets/SEE/GraphProviders/GraphProviderFactory.cs +++ b/Assets/SEE/GraphProviders/GraphProviderFactory.cs @@ -29,7 +29,7 @@ internal static GraphProvider NewInstance(GraphProviderKind kind) GraphProviderKind.MergeDiff => new MergeDiffGraphProvider(), GraphProviderKind.VCS => new VCSGraphProvider(), GraphProviderKind.LSP => new LSPGraphProvider(), - GraphProviderKind.Git => new VCSMetricsProvider(), + GraphProviderKind.VCSMetrics => new VCSMetricsProvider(), _ => throw new NotImplementedException($"Not implemented for {kind}") }; } diff --git a/Assets/SEE/GraphProviders/GraphProviderKind.cs b/Assets/SEE/GraphProviders/GraphProviderKind.cs index 5ef771ceef..7433aea3de 100644 --- a/Assets/SEE/GraphProviders/GraphProviderKind.cs +++ b/Assets/SEE/GraphProviders/GraphProviderKind.cs @@ -48,6 +48,6 @@ public enum GraphProviderKind /// /// For . /// - Git + VCSMetrics } } diff --git a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs index 8664ef63dc..a0adac104f 100644 --- a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs +++ b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs @@ -70,7 +70,7 @@ public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, public override GraphProviderKind GetKind() { - return GraphProviderKind.Git; + return GraphProviderKind.VCSMetrics; } /// From 31ad15e1be5f2dcbfc203edce9741bdc7ab4e916 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 16:04:06 +0200 Subject: [PATCH 13/31] #723 Generalized Git to VCS. Added constant prefix for VCS metrics. --- Assets/SEE/DataModel/DG/StandardNames.cs | 16 ++++++++++------ Assets/SEE/GraphProviders/VCSMetrics.cs | 8 ++++---- Assets/SEETests/TestGraphProviders.cs | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Assets/SEE/DataModel/DG/StandardNames.cs b/Assets/SEE/DataModel/DG/StandardNames.cs index 15f40a9daa..7dbe2772da 100644 --- a/Assets/SEE/DataModel/DG/StandardNames.cs +++ b/Assets/SEE/DataModel/DG/StandardNames.cs @@ -204,25 +204,29 @@ public static class ChangeMarkers } /// - /// Defines names of node attributes for Git metrics. + /// Defines names of node attributes for VCS metrics. /// - public static class Git + public static class VCS { + /// + /// Prefix for VCS metrics. + /// + public const string Prefix = Metrics.Prefix + "VCS."; /// /// The number of lines of code added for a file that was changed between two commits. /// - public const string LinesAdded = "Metric.Git.Lines_Added"; + public const string LinesAdded = Prefix + "Lines_Added"; /// /// The number of lines of code deleted for a file that was changed between two commits. /// - public const string LinesDeleted = "Metric.Git.Lines_Deleted"; + public const string LinesDeleted = Prefix + "Lines_Deleted"; /// /// The number of unique developers who contributed to a file that was changed between two commits. /// - public const string NumberOfDevelopers = "Metric.Git.Number_Of_Developers"; + public const string NumberOfDevelopers = Prefix + "Number_Of_Developers"; /// /// The number of times a file was changed between two commits. /// - public const string CommitFrequency = "Metric.Git.Commit_Frequency"; + public const string CommitFrequency = Prefix + "Commit_Frequency"; } } diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index 8bd2c673a5..ca80d3f815 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -30,8 +30,8 @@ public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit { if (node.ID.Replace('\\', '/') == change.Path) { - node.SetInt(Git.LinesAdded, change.LinesAdded); - node.SetInt(Git.LinesDeleted, change.LinesDeleted); + node.SetInt(DataModel.DG.VCS.LinesAdded, change.LinesAdded); + node.SetInt(DataModel.DG.VCS.LinesDeleted, change.LinesDeleted); } } } @@ -82,7 +82,7 @@ public static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Comm { if (node.ID.Replace('\\', '/') == entry.Key) { - node.SetInt(Git.NumberOfDevelopers, entry.Value.Count); + node.SetInt(DataModel.DG.VCS.NumberOfDevelopers, entry.Value.Count); } } } @@ -136,7 +136,7 @@ public static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit { if (node.ID.Replace('\\', '/') == entry.Key) { - node.SetInt(Git.CommitFrequency, entry.Value); + node.SetInt(DataModel.DG.VCS.CommitFrequency, entry.Value); } } } diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index b6d96fb8a6..33e8175bd9 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -145,7 +145,7 @@ public IEnumerator TestGitGraphProvider() => // Metric from Git. { - Assert.IsTrue(node.TryGetInt(Git.LinesAdded, out int value)); + Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.LinesAdded, out int value)); Assert.AreEqual(40, value); } }); From e1095f9c6fdfdaf2ad2a0abe70669fde24e8da21 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 16:05:30 +0200 Subject: [PATCH 14/31] #723 No need to have fields for VCS info. These arguments will be retrieved from the city parameter each time ProvideAsync() is called. --- .../SEE/GraphProviders/VCSMetricsProvider.cs | 133 +++++------------- 1 file changed, 38 insertions(+), 95 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs index a0adac104f..bff417adc8 100644 --- a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs +++ b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs @@ -16,21 +16,6 @@ namespace SEE.GraphProviders [Serializable] public class VCSMetricsProvider : GraphProvider { - /// - /// The path to the VCS containing the two revisions to be compared. - /// - private string VCSPath = string.Empty; - - /// - /// The older revision that constitutes the baseline of the comparison. - /// - private string OldRevision = string.Empty; - - /// - /// The newer revision against which the is to be compared. - /// - private string NewRevision = string.Empty; - /// /// Calculates metrics between two revisions from a git repository and adds these to . /// The resulting graph is returned. @@ -48,109 +33,67 @@ public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, Action changePercentage = null, CancellationToken token = default) { - CheckArguments(city); if (graph == null) { throw new NotImplementedException(); } - else - { - using (Repository repo = new(VCSPath)) - { - Commit OldCommit = repo.Lookup(OldRevision); - Commit NewCommit = repo.Lookup(NewRevision); - - VCSMetrics.AddLineofCodeChurnMetric(graph, VCSPath, OldCommit, NewCommit); - VCSMetrics.AddNumberofDevelopersMetric(graph, VCSPath, OldCommit, NewCommit); - VCSMetrics.AddCommitFrequencyMetric(graph, VCSPath, OldCommit, NewCommit); - } - return UniTask.FromResult(graph); - } - } - - public override GraphProviderKind GetKind() - { - return GraphProviderKind.VCSMetrics; - } - - /// - /// Checks whether the assumptions on and - /// and and hold. - /// If not, exceptions are thrown accordingly. - /// - /// To be checked - /// thrown in case , - /// or or - /// is undefined or does not exist or is null or is not a DiffCity - protected void CheckArguments(AbstractSEECity city) - { if (city == null) { throw new ArgumentException("The given city is null.\n"); } - else + if (city is DiffCity diffcity) { - if (city is DiffCity diffcity) - { - OldRevision = diffcity.OldRevision; - NewRevision = diffcity.NewRevision; - VCSPath = diffcity.VCSPath.Path; + string oldRevision = diffcity.OldRevision; + string newRevision = diffcity.NewRevision; + string vcsPath = diffcity.VCSPath.Path; - if (string.IsNullOrEmpty(VCSPath)) - { - throw new ArgumentException("Empty VCS Path.\n"); - } - if (!Directory.Exists(VCSPath)) - { - throw new ArgumentException($"Directory {VCSPath} does not exist.\n"); - } - if (string.IsNullOrEmpty(OldRevision)) - { - throw new ArgumentException("Empty Old Revision.\n"); - } - if (string.IsNullOrEmpty(NewRevision)) - { - throw new ArgumentException("Empty New Revision.\n"); - } + if (string.IsNullOrEmpty(vcsPath)) + { + throw new ArgumentException("Empty VCS Path.\n"); } - else + if (!Directory.Exists(vcsPath)) { - throw new ArgumentException($"To generate Git metrics, the given city should be a {nameof(DiffCity)}.\n"); + throw new ArgumentException($"Directory {vcsPath} does not exist.\n"); + } + if (string.IsNullOrEmpty(oldRevision)) + { + throw new ArgumentException("Empty old Revision.\n"); + } + if (string.IsNullOrEmpty(newRevision)) + { + throw new ArgumentException("Empty new Revision.\n"); } - } - } - - #region ConfigIO - /// - /// Label of attribute in the configuration file. - /// - private const string vcsPathLabel = "VCSPath"; + using (Repository repo = new(vcsPath)) + { + Commit oldCommit = repo.Lookup(oldRevision); + Commit newCommit = repo.Lookup(newRevision); - /// - /// Label of attribute in the configuration file. - /// - private const string oldRevisionLabel = "OldRevision"; + VCSMetrics.AddLineofCodeChurnMetric(graph, vcsPath, oldCommit, newCommit); + VCSMetrics.AddNumberofDevelopersMetric(graph, vcsPath, oldCommit, newCommit); + VCSMetrics.AddCommitFrequencyMetric(graph, vcsPath, oldCommit, newCommit); + } + return UniTask.FromResult(graph); + } + else + { + throw new ArgumentException($"To generate VCS metrics, the given city should be a {nameof(DiffCity)}.\n"); + } + } - /// - /// Label of attribute in the configuration file. - /// - private const string newRevisionLabel = "NewRevision"; + public override GraphProviderKind GetKind() + { + return GraphProviderKind.VCSMetrics; + } protected override void SaveAttributes(ConfigWriter writer) { - writer.Save(VCSPath, vcsPathLabel); - writer.Save(OldRevision, oldRevisionLabel); - writer.Save(NewRevision, newRevisionLabel); + // Nothing to be saved. This class has not attributes. } protected override void RestoreAttributes(Dictionary attributes) { - ConfigIO.Restore(attributes, vcsPathLabel, ref VCSPath); - ConfigIO.Restore(attributes, oldRevisionLabel, ref OldRevision); - ConfigIO.Restore(attributes, newRevisionLabel, ref NewRevision); + // Nothing to be restored. This class has not attributes. } - - #endregion } } From 5aba922ea23b4b7e995f6a42fa7a914a89367036 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:00:52 +0200 Subject: [PATCH 15/31] #723 Restructured so that the methods can be called by VCSGraphProvider. --- Assets/SEE/GraphProviders/VCSMetrics.cs | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index ca80d3f815..2356f4d142 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -22,6 +22,11 @@ public static class VCSMetrics public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { using Repository repo = new(vcsPath); + AddLinesOfCodeChurnMetric(graph, repo, oldCommit, newCommit); + } + + private static void AddLinesOfCodeChurnMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) + { Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); foreach (PatchEntryChanges change in changes) @@ -49,6 +54,11 @@ public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit public static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { using Repository repo = new(vcsPath); + AddNumberOfDevelopersMetric(graph, repo, oldCommit, newCommit); + } + + private static void AddNumberOfDevelopersMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) + { ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); Dictionary> uniqueContributorsPerFile = new(); @@ -100,6 +110,11 @@ public static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Comm public static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) { using Repository repo = new(vcsPath); + AddCommitFrequencyMetric(graph, repo, oldCommit, newCommit); + } + + private static void AddCommitFrequencyMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) + { ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = newCommit, @@ -141,5 +156,15 @@ public static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit } } } + + internal static void AddMetrics(Graph graph, Repository repo, string oldRevision, string newRevision) + { + Commit oldCommit = repo.Lookup(oldRevision); + Commit newCommit = repo.Lookup(newRevision); + + AddLinesOfCodeChurnMetric(graph, repo, oldCommit, newCommit); + AddNumberOfDevelopersMetric(graph, repo, oldCommit, newCommit); + AddCommitFrequencyMetric(graph, repo, newCommit, oldCommit); + } } -} \ No newline at end of file +} From 2b4a23c6022732a157a8c34cb6ff80674c58b006 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:14:55 +0200 Subject: [PATCH 16/31] #723 VCS metrics can now be gathered, too. Added attribute BaselineCommitID. --- Assets/SEE/GraphProviders/VCSGraphProvider.cs | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSGraphProvider.cs b/Assets/SEE/GraphProviders/VCSGraphProvider.cs index 04ee0ba92d..8ab74901a9 100644 --- a/Assets/SEE/GraphProviders/VCSGraphProvider.cs +++ b/Assets/SEE/GraphProviders/VCSGraphProvider.cs @@ -23,22 +23,33 @@ namespace SEE.GraphProviders /// Creates a graph based on the content of a version control system. /// Nodes represent directories and files. Their nesting corresponds to /// the directory structure of the repository. Files are leaf nodes. - /// Files nodes contain metrics that can be gathered based on a simple - /// lexical analysis, such as Halstead, McCabe and lines of code. + /// File nodes contain metrics that can be gathered based on a simple + /// lexical analysis, such as Halstead, McCabe and lines of code, as + /// well as from the version control system, such as number of developers, + /// number of commits, or code churn. /// public class VCSGraphProvider : GraphProvider { /// /// The path to the git repository. /// - [ShowInInspector, Tooltip("Path to the git repository."), HideReferenceObjectPicker] + [ShowInInspector, Tooltip("Path to the version control repository."), HideReferenceObjectPicker] public DirectoryPath RepositoryPath = new(); /// /// The commit id. /// - [ShowInInspector, Tooltip("The new commit id."), HideReferenceObjectPicker] - public string CommitID = ""; + [ShowInInspector, Tooltip("The commit id for which to generate the graph."), HideReferenceObjectPicker] + public string CommitID = string.Empty; + + /// + /// The commit id of the baseline. The VCS metrics will be gathered for the time + /// between and . + /// If is null or empty, no VCS metrics are gathered. + /// + [ShowInInspector, Tooltip("VCS metrics will be gathered relative to this commit id. If undefined, no VCS metrics will be gathered"), + HideReferenceObjectPicker] + public string BaselineCommitID = string.Empty; /// /// The list of path globbings to include or exclude files. @@ -67,7 +78,7 @@ public override async UniTask ProvideAsync(Graph graph, AbstractSEECity c CancellationToken token = default) { CheckArguments(city); - return await UniTask.FromResult(GetVCSGraph(PathGlobbing, RepositoryPath.Path, CommitID)); + return await UniTask.FromResult(GetVCSGraph(PathGlobbing, RepositoryPath.Path, CommitID, BaselineCommitID)); } /// @@ -113,9 +124,11 @@ protected void CheckArguments(AbstractSEECity city) /// /// The paths which get included/excluded. /// The path to the repository. - /// The commitID where the files exist. - /// the graph. - private static Graph GetVCSGraph(Dictionary pathGlobbing, string repositoryPath, string commitID) + /// The commit id where the files exist. + /// The commit id of the baseline against which to gather + /// the VCS metrics + /// the resulting graph + private static Graph GetVCSGraph(Dictionary pathGlobbing, string repositoryPath, string commitID, string baselineCommitID) { string[] pathSegments = repositoryPath.Split(Path.DirectorySeparatorChar); @@ -147,7 +160,8 @@ private static Graph GetVCSGraph(Dictionary pathGlobbing, string r BuildGraphFromPath(filePath, null, null, graph, graph.GetNode(pathSegments[^1])); } } - AddMetricsToNode(graph, repo, commitID); + AddCodeMetrics(graph, repo, commitID); + ADDVCSMetrics(graph, repo, baselineCommitID, commitID); } graph.FinalizeNodeHierarchy(); return graph; @@ -340,7 +354,7 @@ public static IEnumerable RetrieveTokens(string filePath, Repository r /// The graph where the metric should be added. /// The repository from which the file content is retrieved. /// The commitID where the files exist. - protected static void AddMetricsToNode(Graph graph, Repository repository, string commitID) + private static void AddCodeMetrics(Graph graph, Repository repository, string commitID) { foreach (Node node in graph.Nodes()) { @@ -373,6 +387,25 @@ protected static void AddMetricsToNode(Graph graph, Repository repository, strin } } + /// + /// Adds VCS metrics to all nodes in based on the + /// VCS information derived from . The metrics are gathered + /// in between the and . + /// If is null or empty, no metrics will be + /// gathered. + /// + /// The graph where the metric should be added. + /// The repository from which the file content is retrieved. + /// The starting commit ID (baseline). + /// The ending commit. + private static void ADDVCSMetrics(Graph graph, Repository repository, string oldCommit, string newCommit) + { + if (!string.IsNullOrWhiteSpace(oldCommit)) + { + VCSMetrics.AddMetrics(graph, repository, oldCommit, newCommit); + } + } + #region Config I/O /// @@ -386,14 +419,19 @@ protected static void AddMetricsToNode(Graph graph, Repository repository, strin private const string repositoryPathLabel = "RepositoryPath"; /// - /// Label of attribute in the configuration file. + /// Label of attribute in the configuration file. /// private const string commitIDLabel = "CommitID"; + /// + /// Label of attribute in the configuration file. + /// + private const string baselineCommitIDLabel = "BaselineCommitID"; protected override void SaveAttributes(ConfigWriter writer) { writer.Save(PathGlobbing, pathGlobbingLabel); writer.Save(CommitID, commitIDLabel); + writer.Save(BaselineCommitID, baselineCommitIDLabel); RepositoryPath.Save(writer, repositoryPathLabel); } @@ -401,6 +439,7 @@ protected override void RestoreAttributes(Dictionary attributes) { ConfigIO.Restore(attributes, pathGlobbingLabel, ref PathGlobbing); ConfigIO.Restore(attributes, commitIDLabel, ref CommitID); + ConfigIO.Restore(attributes, baselineCommitIDLabel, ref BaselineCommitID); RepositoryPath.Restore(attributes, repositoryPathLabel); } From 48deb36e1019fc368a1e5390217fb1f8ea81cd1c Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:15:37 +0200 Subject: [PATCH 17/31] #723 Added documentation. Add more precondition checks. --- Assets/SEE/GraphProviders/VCSMetrics.cs | 42 +++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index 2356f4d142..60d8c6c30f 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -157,14 +157,44 @@ private static void AddCommitFrequencyMetric(Graph graph, Repository repo, Commi } } - internal static void AddMetrics(Graph graph, Repository repo, string oldRevision, string newRevision) + /// + /// Adds VCS metrics to all nodes in based on the + /// VCS information derived from . The metrics are gathered + /// in between the and . + /// If or is null or empty, + /// an exception is thrown. + /// + /// The graph where the metric should be added. + /// The repository from which the file content is retrieved. + /// The starting commit ID (baseline). + /// The ending commit. + /// thrown if + /// or is null or empty or if they do not + /// refer to an existing revision + internal static void AddMetrics(Graph graph, Repository repository, string oldRevision, string newRevision) { - Commit oldCommit = repo.Lookup(oldRevision); - Commit newCommit = repo.Lookup(newRevision); + if (string.IsNullOrWhiteSpace(oldRevision)) + { + throw new System.Exception("The old revision must neither be null nor empty."); + } + if (string.IsNullOrWhiteSpace(newRevision)) + { + throw new System.Exception("The new revision must neither be null nor empty."); + } + Commit oldCommit = repository.Lookup(oldRevision); + if (oldCommit == null) + { + throw new System.Exception($"There is no revision {oldCommit}"); + } + Commit newCommit = repository.Lookup(newRevision); + if (newCommit == null) + { + throw new System.Exception($"There is no revision {newCommit}"); + } - AddLinesOfCodeChurnMetric(graph, repo, oldCommit, newCommit); - AddNumberOfDevelopersMetric(graph, repo, oldCommit, newCommit); - AddCommitFrequencyMetric(graph, repo, newCommit, oldCommit); + AddLinesOfCodeChurnMetric(graph, repository, oldCommit, newCommit); + AddNumberOfDevelopersMetric(graph, repository, oldCommit, newCommit); + AddCommitFrequencyMetric(graph, repository, newCommit, oldCommit); } } } From 4035c0ed6823c46509965dc2a48425739c2338b0 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:16:31 +0200 Subject: [PATCH 18/31] #723 The BaselineCommitID is provided for the VCSCity. --- Assets/Scenes/SEENewWorld.unity | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Assets/Scenes/SEENewWorld.unity b/Assets/Scenes/SEENewWorld.unity index b876cf600b..2034e722af 100644 --- a/Assets/Scenes/SEENewWorld.unity +++ b/Assets/Scenes/SEENewWorld.unity @@ -34816,6 +34816,9 @@ MonoBehaviour: - Name: CommitID Entry: 1 Data: 0878f91f900dc90d89c594c521ac1d3b9edd7097 + - Name: BaselineCommitID + Entry: 1 + Data: a5fe5e6a2692f41aeb8448d5114000e6f82e605e - Name: PathGlobbing Entry: 7 Data: 29|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Boolean, From 6a6035c1d3006205f4f2ff9a62f09ab4db08358a Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:16:44 +0200 Subject: [PATCH 19/31] #723 The BaselineCommitID is provided for the VCSCity. --- Assets/StreamingAssets/SEE/SEEVCS.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/StreamingAssets/SEE/SEEVCS.cfg b/Assets/StreamingAssets/SEE/SEEVCS.cfg index 2c93f5aa41..b2e9baabbc 100644 --- a/Assets/StreamingAssets/SEE/SEEVCS.cfg +++ b/Assets/StreamingAssets/SEE/SEEVCS.cfg @@ -214,6 +214,7 @@ data : { ]; ]; CommitID : "0878f91f900dc90d89c594c521ac1d3b9edd7097"; + BaselineCommitID : "a5fe5e6a2692f41aeb8448d5114000e6f82e605e"; RepositoryPath : { Root : "ProjectFolder"; RelativePath : ""; From 7e1ccd3005dac66782338f298a4b7c12944c0f65 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 17:34:59 +0200 Subject: [PATCH 20/31] #732 Removed VCSMetricsProvider. It is now part of VCSGraphProvider. --- .../GraphProviders/GraphProviderFactory.cs | 1 - .../SEE/GraphProviders/VCSMetricsProvider.cs | 99 ------------------- .../GraphProviders/VCSMetricsProvider.cs.meta | 11 --- Assets/SEETests/TestGraphProviders.cs | 21 ++-- 4 files changed, 15 insertions(+), 117 deletions(-) delete mode 100644 Assets/SEE/GraphProviders/VCSMetricsProvider.cs delete mode 100644 Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta diff --git a/Assets/SEE/GraphProviders/GraphProviderFactory.cs b/Assets/SEE/GraphProviders/GraphProviderFactory.cs index 8e2deedc45..e13210375e 100644 --- a/Assets/SEE/GraphProviders/GraphProviderFactory.cs +++ b/Assets/SEE/GraphProviders/GraphProviderFactory.cs @@ -29,7 +29,6 @@ internal static GraphProvider NewInstance(GraphProviderKind kind) GraphProviderKind.MergeDiff => new MergeDiffGraphProvider(), GraphProviderKind.VCS => new VCSGraphProvider(), GraphProviderKind.LSP => new LSPGraphProvider(), - GraphProviderKind.VCSMetrics => new VCSMetricsProvider(), _ => throw new NotImplementedException($"Not implemented for {kind}") }; } diff --git a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs deleted file mode 100644 index bff417adc8..0000000000 --- a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Cysharp.Threading.Tasks; -using LibGit2Sharp; -using SEE.DataModel.DG; -using SEE.Game.City; -using SEE.Utils.Config; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; - -namespace SEE.GraphProviders -{ - /// - /// Calculates metrics between two revisions from a git repository and adds these to a graph. - /// - [Serializable] - public class VCSMetricsProvider : GraphProvider - { - /// - /// Calculates metrics between two revisions from a git repository and adds these to . - /// The resulting graph is returned. - /// - /// an existing graph where to add the metrics - /// this value is currently ignored - /// this value is currently ignored - /// this value is currently ignored - /// the input with metrics added - /// thrown in case - /// is undefined or does not exist or is null - /// thrown in case is - /// null; this is currently not supported. - public override UniTask ProvideAsync(Graph graph, AbstractSEECity city, - Action changePercentage = null, - CancellationToken token = default) - { - if (graph == null) - { - throw new NotImplementedException(); - } - if (city == null) - { - throw new ArgumentException("The given city is null.\n"); - } - if (city is DiffCity diffcity) - { - string oldRevision = diffcity.OldRevision; - string newRevision = diffcity.NewRevision; - string vcsPath = diffcity.VCSPath.Path; - - if (string.IsNullOrEmpty(vcsPath)) - { - throw new ArgumentException("Empty VCS Path.\n"); - } - if (!Directory.Exists(vcsPath)) - { - throw new ArgumentException($"Directory {vcsPath} does not exist.\n"); - } - if (string.IsNullOrEmpty(oldRevision)) - { - throw new ArgumentException("Empty old Revision.\n"); - } - if (string.IsNullOrEmpty(newRevision)) - { - throw new ArgumentException("Empty new Revision.\n"); - } - - using (Repository repo = new(vcsPath)) - { - Commit oldCommit = repo.Lookup(oldRevision); - Commit newCommit = repo.Lookup(newRevision); - - VCSMetrics.AddLineofCodeChurnMetric(graph, vcsPath, oldCommit, newCommit); - VCSMetrics.AddNumberofDevelopersMetric(graph, vcsPath, oldCommit, newCommit); - VCSMetrics.AddCommitFrequencyMetric(graph, vcsPath, oldCommit, newCommit); - } - return UniTask.FromResult(graph); - } - else - { - throw new ArgumentException($"To generate VCS metrics, the given city should be a {nameof(DiffCity)}.\n"); - } - } - - public override GraphProviderKind GetKind() - { - return GraphProviderKind.VCSMetrics; - } - - protected override void SaveAttributes(ConfigWriter writer) - { - // Nothing to be saved. This class has not attributes. - } - - protected override void RestoreAttributes(Dictionary attributes) - { - // Nothing to be restored. This class has not attributes. - } - } -} diff --git a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta b/Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta deleted file mode 100644 index 96e900bb6e..0000000000 --- a/Assets/SEE/GraphProviders/VCSMetricsProvider.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 508dcb5d796d13c49ac65298ee6f24a7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index 33e8175bd9..620b3334dc 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -4,6 +4,7 @@ using SEE.Game.City; using SEE.Utils.Paths; using System.Collections; +using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.TestTools; @@ -126,16 +127,24 @@ public IEnumerator TestMergeDiffGraphProvider() => }); [UnityTest] - public IEnumerator TestGitGraphProvider() => + public IEnumerator TestVCSMetrics() => UniTask.ToCoroutine(async () => { GameObject go = new(); - DiffCity city = go.AddComponent(); - city.VCSPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)); - city.OldRevision = "887e1fc1d6fe87ee1178822b5eeb666e62af3710"; - city.NewRevision = "5efa95913a6e894e5340f07fab26c9958b5c1096"; + SEECity city = go.AddComponent(); + + Dictionary pathGlobbing = new() + { + { "Assets/SEE/**/*.cs", true } + }; - GraphProvider provider = new VCSMetricsProvider(); + VCSGraphProvider provider = new() + { + RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), + BaselineCommitID = "887e1fc1d6fe87ee1178822b5eeb666e62af3710", + CommitID = "5efa95913a6e894e5340f07fab26c9958b5c1096", + PathGlobbing = pathGlobbing + }; Graph loaded = await provider.ProvideAsync(new Graph(""), city); Assert.IsNotNull(loaded); From de29cb02f1e0714207f4a4311f7ad619f51482cb Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 19:10:17 +0200 Subject: [PATCH 21/31] #723 Introduced constant names for Halstead metrics. --- Assets/SEE/DataModel/DG/StandardNames.cs | 68 +++++++++++++------ Assets/SEE/GraphProviders/VCSGraphProvider.cs | 30 ++++---- Assets/SEETests/TestGraphIO.cs | 2 +- 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/Assets/SEE/DataModel/DG/StandardNames.cs b/Assets/SEE/DataModel/DG/StandardNames.cs index 7dbe2772da..f670eb75cb 100644 --- a/Assets/SEE/DataModel/DG/StandardNames.cs +++ b/Assets/SEE/DataModel/DG/StandardNames.cs @@ -64,6 +64,30 @@ public static string Name(this NumericAttributeNames numericAttributeName) } } + /// + /// Provides a common prefix for all Halstead metrics. + /// + public static class Halstead + { + /// + /// Prefix for all metrics. + /// + public const string Prefix = Metrics.Prefix + "Halstead."; + + public const string DistinctOperators = Prefix + "Distinct_Operators"; + public const string DistinctOperands = Prefix + "Distinct_Operands"; + public const string TotalOperators = Prefix + "Total_Operators"; + public const string TotalOperands = Prefix + "Total_Operands"; + public const string ProgramVocabulary = Prefix + "Program_Vocabulary"; + public const string ProgramLength = Prefix + "Program_Length"; + public const string EstimatedProgramLength = Prefix + "Estimated_Program_Length"; + public const string Volume = Prefix + "Volume"; + public const string Difficulty = Prefix + "Difficulty"; + public const string Effort = Prefix + "Effort"; + public const string TimeRequiredToProgram = Prefix + "Time_Required_To_Program"; + public const string NumberOfDeliveredBugs = Prefix + "Number_Of_Delivered_Bugs"; + } + /// /// Defines names of node attributes for JaCoCo code-coverage metrics. /// See also: https://www.eclemma.org/jacoco/trunk/doc/counters.html @@ -181,28 +205,6 @@ public static class JaCoCo public const string PercentageOfClassCovered = Prefix + "CLASS_percentage"; } - /// - /// Defines toggle attributes used to mark nodes as to whether they have been - /// changed, deleted, or added as new. - /// - public static class ChangeMarkers - { - /// - /// Name of the toggle marking a graph element as new (existing only in the newer version). - /// - public const string IsNew = "Change.IsNew"; - /// - /// Name of the toggle marking a graph element as deleted (existing only in the baseline version). - /// - public const string IsDeleted = "Change.IsDeleted"; - /// - /// Name of the toggle marking a graph element as changed (existing in both the newer and baseline - /// version). At least one numeric attribute has changed between the two (including the addition - /// or removal of an attribute). - /// - public const string IsChanged = "Change.IsChanged"; - } - /// /// Defines names of node attributes for VCS metrics. /// @@ -229,4 +231,26 @@ public static class VCS /// public const string CommitFrequency = Prefix + "Commit_Frequency"; } + + /// + /// Defines toggle attributes used to mark nodes as to whether they have been + /// changed, deleted, or added as new. + /// + public static class ChangeMarkers + { + /// + /// Name of the toggle marking a graph element as new (existing only in the newer version). + /// + public const string IsNew = "Change.IsNew"; + /// + /// Name of the toggle marking a graph element as deleted (existing only in the baseline version). + /// + public const string IsDeleted = "Change.IsDeleted"; + /// + /// Name of the toggle marking a graph element as changed (existing in both the newer and baseline + /// version). At least one numeric attribute has changed between the two (including the addition + /// or removal of an attribute). + /// + public const string IsChanged = "Change.IsChanged"; + } } diff --git a/Assets/SEE/GraphProviders/VCSGraphProvider.cs b/Assets/SEE/GraphProviders/VCSGraphProvider.cs index 8ab74901a9..8618efe323 100644 --- a/Assets/SEE/GraphProviders/VCSGraphProvider.cs +++ b/Assets/SEE/GraphProviders/VCSGraphProvider.cs @@ -365,23 +365,21 @@ private static void AddCodeMetrics(Graph graph, Repository repository, string co if (language != TokenLanguage.Plain) { IEnumerable tokens = RetrieveTokens(filePath, repository, commitID, language); - int complexity = TokenMetrics.CalculateMcCabeComplexity(tokens); - int linesOfCode = TokenMetrics.CalculateLinesOfCode(tokens); + node.SetInt(Metrics.Prefix + "LOC", TokenMetrics.CalculateLinesOfCode(tokens)); + node.SetInt(Metrics.Prefix + "McCabe_Complexity", TokenMetrics.CalculateMcCabeComplexity(tokens)); TokenMetrics.HalsteadMetrics halsteadMetrics = TokenMetrics.CalculateHalsteadMetrics(tokens); - node.SetInt(Metrics.Prefix + "LOC", linesOfCode); - node.SetInt(Metrics.Prefix + "McCabe_Complexity", complexity); - node.SetInt(Metrics.Prefix + "Halstead.Distinct_Operators", halsteadMetrics.DistinctOperators); - node.SetInt(Metrics.Prefix + "Halstead.Distinct_Operands", halsteadMetrics.DistinctOperands); - node.SetInt(Metrics.Prefix + "Halstead.Total_Operators", halsteadMetrics.TotalOperators); - node.SetInt(Metrics.Prefix + "Halstead.Total_Operands", halsteadMetrics.TotalOperands); - node.SetInt(Metrics.Prefix + "Halstead.Program_Vocabulary", halsteadMetrics.ProgramVocabulary); - node.SetInt(Metrics.Prefix + "Halstead.Program_Length", halsteadMetrics.ProgramLength); - node.SetFloat(Metrics.Prefix + "Halstead.Estimated_Program_Length", halsteadMetrics.EstimatedProgramLength); - node.SetFloat(Metrics.Prefix + "Halstead.Volume", halsteadMetrics.Volume); - node.SetFloat(Metrics.Prefix + "Halstead.Difficulty", halsteadMetrics.Difficulty); - node.SetFloat(Metrics.Prefix + "Halstead.Effort", halsteadMetrics.Effort); - node.SetFloat(Metrics.Prefix + "Halstead.Time_Required_To_Program", halsteadMetrics.TimeRequiredToProgram); - node.SetFloat(Metrics.Prefix + "Halstead.Number_Of_Delivered_Bugs", halsteadMetrics.NumberOfDeliveredBugs); + node.SetInt(Halstead.DistinctOperators, halsteadMetrics.DistinctOperators); + node.SetInt(Halstead.DistinctOperands, halsteadMetrics.DistinctOperands); + node.SetInt(Halstead.TotalOperators, halsteadMetrics.TotalOperators); + node.SetInt(Halstead.TotalOperands, halsteadMetrics.TotalOperands); + node.SetInt(Halstead.ProgramVocabulary, halsteadMetrics.ProgramVocabulary); + node.SetInt(Halstead.ProgramLength, halsteadMetrics.ProgramLength); + node.SetFloat(Halstead.EstimatedProgramLength, halsteadMetrics.EstimatedProgramLength); + node.SetFloat(Halstead.Volume, halsteadMetrics.Volume); + node.SetFloat(Halstead.Difficulty, halsteadMetrics.Difficulty); + node.SetFloat(Halstead.Effort, halsteadMetrics.Effort); + node.SetFloat(Halstead.TimeRequiredToProgram, halsteadMetrics.TimeRequiredToProgram); + node.SetFloat(Halstead.NumberOfDeliveredBugs, halsteadMetrics.NumberOfDeliveredBugs); } } } diff --git a/Assets/SEETests/TestGraphIO.cs b/Assets/SEETests/TestGraphIO.cs index 3c09c82ffd..4006392ed0 100644 --- a/Assets/SEETests/TestGraphIO.cs +++ b/Assets/SEETests/TestGraphIO.cs @@ -218,7 +218,7 @@ private static Node NewNode(Graph graph, string linkname) }; result.SetToggle("Linkage.Is_Definition"); result.SetString("stringAttribute", "somestring"); - result.SetFloat(Metrics.Prefix + "Halstead.Volume", 49.546f); + result.SetFloat(Halstead.Volume, 49.546f); result.SetInt(Metrics.Prefix + "LOC", 10); graph.AddNode(result); return result; From 3742dc36237e3357e66c25a252af4cf10486b570 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 19:11:45 +0200 Subject: [PATCH 22/31] #723 AddMetrics moved to the top of the file. Removed unused methods. --- Assets/SEE/GraphProviders/VCSMetrics.cs | 121 ++++++++++-------------- 1 file changed, 51 insertions(+), 70 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index 60d8c6c30f..485b7cb6aa 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -11,23 +11,56 @@ namespace SEE.GraphProviders /// public static class VCSMetrics { + /// + /// Adds VCS metrics to all nodes in based on the + /// VCS information derived from . The metrics are gathered + /// in between the and . + /// If or is null or empty, + /// an exception is thrown. + /// + /// The graph where the metric should be added. + /// The repository from which the file content is retrieved. + /// The starting commit ID (baseline). + /// The ending commit. + /// thrown if + /// or is null or empty or if they do not + /// refer to an existing revision + internal static void AddMetrics(Graph graph, Repository repository, string oldRevision, string newRevision) + { + if (string.IsNullOrWhiteSpace(oldRevision)) + { + throw new System.Exception("The old revision must neither be null nor empty."); + } + if (string.IsNullOrWhiteSpace(newRevision)) + { + throw new System.Exception("The new revision must neither be null nor empty."); + } + Commit oldCommit = repository.Lookup(oldRevision); + if (oldCommit == null) + { + throw new System.Exception($"There is no revision {oldCommit}"); + } + Commit newCommit = repository.Lookup(newRevision); + if (newCommit == null) + { + throw new System.Exception($"There is no revision {newCommit}"); + } + + AddLinesOfCodeChurnMetric(graph, repository, oldCommit, newCommit); + AddNumberOfDevelopersMetric(graph, repository, oldCommit, newCommit); + AddCommitFrequencyMetric(graph, repository, oldCommit, newCommit); + } + /// /// Calculates the number of lines of code added and deleted for each file changed /// between two commits and adds them as metrics to . /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared + /// the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison /// the newer commit against which the is - /// to be compared - public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) + private static void AddLinesOfCodeChurnMetric(Graph graph, Repository repository, Commit oldCommit, Commit newCommit) { - using Repository repo = new(vcsPath); - AddLinesOfCodeChurnMetric(graph, repo, oldCommit, newCommit); - } - - private static void AddLinesOfCodeChurnMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) - { - Patch changes = repo.Diff.Compare(oldCommit.Tree, newCommit.Tree); + Patch changes = repository.Diff.Compare(oldCommit.Tree, newCommit.Tree); foreach (PatchEntryChanges change in changes) { @@ -47,19 +80,13 @@ private static void AddLinesOfCodeChurnMetric(Graph graph, Repository repo, Comm /// between two commits and adds it as a metric to . /// /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared + /// the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison /// the newer commit against which the is /// to be compared - public static void AddNumberofDevelopersMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) - { - using Repository repo = new(vcsPath); - AddNumberOfDevelopersMetric(graph, repo, oldCommit, newCommit); - } - - private static void AddNumberOfDevelopersMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) + private static void AddNumberOfDevelopersMetric(Graph graph, Repository repository, Commit oldCommit, Commit newCommit) { - ICommitLog commits = repo.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); + ICommitLog commits = repository.Commits.QueryBy(new CommitFilter { SortBy = CommitSortStrategies.Topological }); Dictionary> uniqueContributorsPerFile = new(); @@ -69,7 +96,7 @@ private static void AddNumberOfDevelopersMetric(Graph graph, Repository repo, Co { foreach (Commit parent in commit.Parents) { - Patch changes = repo.Diff.Compare(parent.Tree, commit.Tree); + Patch changes = repository.Diff.Compare(parent.Tree, commit.Tree); foreach (PatchEntryChanges change in changes) { @@ -103,19 +130,13 @@ private static void AddNumberOfDevelopersMetric(Graph graph, Repository repo, Co /// two commits and adds it as a metric to . /// /// an existing graph where to add the metrics - /// the path to the VCS containing the two revisions to be compared + /// the VCS containing the two revisions to be compared /// the older commit that constitutes the baseline of the comparison /// the newer commit against which the is /// to be compared - public static void AddCommitFrequencyMetric(Graph graph, string vcsPath, Commit oldCommit, Commit newCommit) - { - using Repository repo = new(vcsPath); - AddCommitFrequencyMetric(graph, repo, oldCommit, newCommit); - } - - private static void AddCommitFrequencyMetric(Graph graph, Repository repo, Commit oldCommit, Commit newCommit) + private static void AddCommitFrequencyMetric(Graph graph, Repository repository, Commit oldCommit, Commit newCommit) { - ICommitLog commitsBetween = repo.Commits.QueryBy(new CommitFilter + ICommitLog commitsBetween = repository.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = newCommit, ExcludeReachableFrom = oldCommit @@ -127,7 +148,7 @@ private static void AddCommitFrequencyMetric(Graph graph, Repository repo, Commi { foreach (Commit parent in commit.Parents) { - TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); + TreeChanges changes = repository.Diff.Compare(parent.Tree, commit.Tree); foreach (TreeEntryChanges change in changes) { @@ -156,45 +177,5 @@ private static void AddCommitFrequencyMetric(Graph graph, Repository repo, Commi } } } - - /// - /// Adds VCS metrics to all nodes in based on the - /// VCS information derived from . The metrics are gathered - /// in between the and . - /// If or is null or empty, - /// an exception is thrown. - /// - /// The graph where the metric should be added. - /// The repository from which the file content is retrieved. - /// The starting commit ID (baseline). - /// The ending commit. - /// thrown if - /// or is null or empty or if they do not - /// refer to an existing revision - internal static void AddMetrics(Graph graph, Repository repository, string oldRevision, string newRevision) - { - if (string.IsNullOrWhiteSpace(oldRevision)) - { - throw new System.Exception("The old revision must neither be null nor empty."); - } - if (string.IsNullOrWhiteSpace(newRevision)) - { - throw new System.Exception("The new revision must neither be null nor empty."); - } - Commit oldCommit = repository.Lookup(oldRevision); - if (oldCommit == null) - { - throw new System.Exception($"There is no revision {oldCommit}"); - } - Commit newCommit = repository.Lookup(newRevision); - if (newCommit == null) - { - throw new System.Exception($"There is no revision {newCommit}"); - } - - AddLinesOfCodeChurnMetric(graph, repository, oldCommit, newCommit); - AddNumberOfDevelopersMetric(graph, repository, oldCommit, newCommit); - AddCommitFrequencyMetric(graph, repository, newCommit, oldCommit); - } } } From 1a79e9313f1b4fcaac5c5267acd46ec7158fc8d3 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 19:12:47 +0200 Subject: [PATCH 23/31] #723 TestGraphProviderIO is for testing only loading/saving the config data. --- Assets/SEETests/TestGraphProviderIO.cs | 162 +++++-------------------- 1 file changed, 28 insertions(+), 134 deletions(-) diff --git a/Assets/SEETests/TestGraphProviderIO.cs b/Assets/SEETests/TestGraphProviderIO.cs index 267e6f06af..b00b4825cb 100644 --- a/Assets/SEETests/TestGraphProviderIO.cs +++ b/Assets/SEETests/TestGraphProviderIO.cs @@ -1,8 +1,5 @@ -using LibGit2Sharp; -using NUnit.Framework; +using NUnit.Framework; using SEE.DataModel.DG; -using SEE.Game.City; -using SEE.Scanner; using SEE.Utils; using SEE.Utils.Config; using SEE.Utils.Paths; @@ -248,7 +245,7 @@ private MergeDiffGraphProvider GetDiffMergeProvider() { OldGraph = new JaCoCoGraphProvider() { - Path = new Utils.Paths.FilePath(Application.streamingAssetsPath + "/mydir/jacoco.xml") + Path = new FilePath(Application.streamingAssetsPath + "/mydir/jacoco.xml") } }; } @@ -264,155 +261,52 @@ private static void AreEqualDiffMergeGraphProviders(MergeDiffGraphProvider saved #region VCSGraphProvider - public async Task GetVCSGraphAsync() + public void TestVCSGraphProvider() { VCSGraphProvider saved = GetVCSGraphProvider(); - SEECity testCity = NewVanillaSEECity(); ; - Graph testGraph = new("test", "test"); - Graph graph = await saved.ProvideAsync(testGraph, testCity); - return graph; + Save(saved); + AreEqualVCSGraphProviders(saved, Load()); } - [Test] - public async Task TestVCSGraphProviderAsync() + private void AreEqualVCSGraphProviders(VCSGraphProvider saved, GraphProvider loaded) { - Graph graph = await GetVCSGraphAsync(); - List pathsFromGraph = new(); - foreach (GraphElement elem in graph.Elements()) - { - pathsFromGraph.Add(elem.ID); - } - - string repositoryPath = Application.dataPath; - string projectPath = repositoryPath.Substring(0, repositoryPath.LastIndexOf("/")); - string projectName = Path.GetFileName(projectPath); + Assert.IsTrue(saved.GetType() == loaded.GetType()); + VCSGraphProvider loadedProvider = loaded as VCSGraphProvider; + AreEqual(saved.RepositoryPath, loadedProvider.RepositoryPath); + Assert.AreEqual(saved.CommitID, loadedProvider.CommitID); + Assert.AreEqual(saved.BaselineCommitID, loadedProvider.BaselineCommitID); + AreEqual(saved.PathGlobbing, loadedProvider.PathGlobbing); + } - List actualList = new() + private void AreEqual(IDictionary expected, IDictionary actual) + { + Assert.AreEqual(expected.Count, actual.Count); + foreach (var kv in expected) { - projectName, - ".gitignore", - "Assets", - "Assets/Scenes.meta", - "Assets/Scenes", - "Assets/Scenes/SampleScene.unity", - "Assets/Scenes/SampleScene.unity.meta", - "Packages", - "Packages/manifest.json", - "ProjectSettings", - "ProjectSettings/AudioManager.asset", - "ProjectSettings/ClusterInputManager.asset", - "ProjectSettings/DynamicsManager.asset", - "ProjectSettings/EditorBuildSettings.asset", - "ProjectSettings/EditorSettings.asset", - "ProjectSettings/GraphicsSettings.asset", - "ProjectSettings/InputManager.asset", - "ProjectSettings/NavMeshAreas.asset", - "ProjectSettings/Physics2DSettings.asset", - "ProjectSettings/PresetManager.asset", - "ProjectSettings/ProjectSettings.asset", - "ProjectSettings/ProjectVersion.txt", - "ProjectSettings/QualitySettings.asset", - "ProjectSettings/TagManager.asset", - "ProjectSettings/TimeManager.asset", - "ProjectSettings/UnityConnectSettings.asset", - "ProjectSettings/VFXManager.asset", - "ProjectSettings/XRSettings.asset" - }; - Assert.AreEqual(28, pathsFromGraph.Count()); - Assert.IsTrue(actualList.OrderByDescending(x => x).ToList().SequenceEqual(pathsFromGraph.OrderByDescending(x => x).ToList())); + Assert.IsTrue(actual.TryGetValue(kv.Key, out bool value)); + Assert.AreEqual(kv.Value, value); + } } private VCSGraphProvider GetVCSGraphProvider() { + Dictionary pathGlobbing = new() + { + { "Assets/SEE/**/*.cs", true } + }; + return new VCSGraphProvider() { RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), CommitID = "b10e1f49c144c0a22aa0d972c946f93a82ad3461", - }; - } - - [Test] - public async Task TestRetrieveTokensAsync() - { - Graph graph = await GetVCSGraphAsync(); - Node fileNode = graph.Nodes().First(t => t.Type == "File"); - string filePath = fileNode.ID; - string commitID = "b10e1f49c144c0a22aa0d972c946f93a82ad3461"; - string repoPath = Path.GetDirectoryName(Application.dataPath); - using Repository repo = new(repoPath); - TokenLanguage language = TokenLanguage.FromFileExtension(Path.GetExtension(filePath).TrimStart('.')); - - IEnumerable tokens = VCSGraphProvider.RetrieveTokens(filePath, repo, commitID, language); - - Assert.IsNotNull(tokens); - Assert.NotZero(tokens.Count()); - } + BaselineCommitID = "5efa95913a6e894e5340f07fab26c9958b5c1096", + PathGlobbing = pathGlobbing - [Test] - public async Task TestAddMetricsToNodeAsync() - { - Graph graph = await GetVCSGraphAsync(); - string repoPath = Path.GetDirectoryName(Application.dataPath); - string commitID = "b10e1f49c144c0a22aa0d972c946f93a82ad3461"; - using Repository repo = new(repoPath); - - foreach (Node node in graph.Nodes()) - { - string filePath = node.ID.Replace('\\', '/'); - TokenLanguage language = TokenLanguage.FromFileExtension(Path.GetExtension(filePath).TrimStart('.')); - if (node.Type == "File" && language != TokenLanguage.Plain) - { - IEnumerable tokens = VCSGraphProvider.RetrieveTokens(filePath, repo, commitID, language); - AssertMetricsCanBeAdded(node); - } - - AssertMetricsCannotBeAdded(node); - } - } - - private static void AssertMetricsCanBeAdded(Node node) - { - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "LOD")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "McCabe_Complexity")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Distinct_Operators")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Distinct_Operands")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Total_Operators")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Total_Operands")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Program_Vocabulary")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Program_Length")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Estimated_Program_Length")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Volume")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Difficulty")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Effort")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Time_Required_To_Program")); - Assert.IsTrue(node.HasToggle(Metrics.Prefix + "Halstead.Number_Of_Delivered_Bugs")); - } - - private static void AssertMetricsCannotBeAdded(Node node) - { - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "LOC")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "McCabe_Complexity")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Distinct_Operators")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Distinct_Operands")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Total_Operators")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Total_Operands")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Program_Vocabulary")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Program_Length")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Estimated_Program_Length")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Volume")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Difficulty")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Effort")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Time_Required_To_Program")); - Assert.IsFalse(node.HasToggle(Metrics.Prefix + "Halstead.Number_Of_Delivered_Bugs")); + }; } #endregion - private static T NewVanillaSEECity() where T : Component - { - return new GameObject().AddComponent(); - } - private GraphProvider Load() { using ConfigReader stream = new(filename); From c60474980cab3496658d15b9b98fc200979b1816 Mon Sep 17 00:00:00 2001 From: "koschke@uni-bremen.de" Date: Wed, 12 Jun 2024 19:14:06 +0200 Subject: [PATCH 24/31] #723 Reorganized and fixed the test cases for VCSGraphProvider. The test TestVCSGraphProviderAsync needs further adjustments. --- Assets/SEETests/TestGraphProviders.cs | 147 ++++++++++++++++++++++---- 1 file changed, 126 insertions(+), 21 deletions(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index 620b3334dc..f194965b3b 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -1,11 +1,15 @@ using Cysharp.Threading.Tasks; +using LibGit2Sharp; using NUnit.Framework; using SEE.DataModel.DG; using SEE.Game.City; +using SEE.Scanner; using SEE.Utils.Paths; using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.TestTools; @@ -126,37 +130,138 @@ public IEnumerator TestMergeDiffGraphProvider() => } }); + [Test] + public async Task TestVCSGraphProviderAsync() + { + Graph graph = await GetVCSGraphAsync(); + + List pathsFromGraph = new(); + foreach (GraphElement elem in graph.Elements()) + { + pathsFromGraph.Add(elem.ID); + } + const int numberOfCSFiles = 759; + Assert.AreEqual(numberOfCSFiles, pathsFromGraph.Count()); + + string repositoryPath = Application.dataPath; + string projectPath = repositoryPath[..repositoryPath.LastIndexOf("/")]; + string projectName = Path.GetFileName(projectPath); + + List actualList = new() + { + projectName, + ".gitignore", + "Assets", + "Assets/Scenes.meta", + "Assets/Scenes", + "Assets/Scenes/SampleScene.unity", + "Assets/Scenes/SampleScene.unity.meta", + "Packages", + "Packages/manifest.json", + "ProjectSettings", + "ProjectSettings/AudioManager.asset", + "ProjectSettings/ClusterInputManager.asset", + "ProjectSettings/DynamicsManager.asset", + "ProjectSettings/EditorBuildSettings.asset", + "ProjectSettings/EditorSettings.asset", + "ProjectSettings/GraphicsSettings.asset", + "ProjectSettings/InputManager.asset", + "ProjectSettings/NavMeshAreas.asset", + "ProjectSettings/Physics2DSettings.asset", + "ProjectSettings/PresetManager.asset", + "ProjectSettings/ProjectSettings.asset", + "ProjectSettings/ProjectVersion.txt", + "ProjectSettings/QualitySettings.asset", + "ProjectSettings/TagManager.asset", + "ProjectSettings/TimeManager.asset", + "ProjectSettings/UnityConnectSettings.asset", + "ProjectSettings/VFXManager.asset", + "ProjectSettings/XRSettings.asset" + }; + + Assert.IsTrue(actualList.OrderByDescending(x => x).ToList().SequenceEqual(pathsFromGraph.OrderByDescending(x => x).ToList())); + } + + /// + /// Checks whether a random file has the token metrics we expect. + /// Note that we do not evaluate their values. This kind of test is + /// is done in the test case . + /// + /// + [Test] + public async Task TestExistenceOfTokenMetricsAsync() + { + Graph graph = await GetVCSGraphAsync(); + Node fileNode = graph.Nodes().First(t => t.Type == "File"); + AssertTokenMetricsExist(fileNode); + } + + private static void AssertTokenMetricsExist(Node node) + { + Debug.Log(node.ToString()); + Assert.IsTrue(node.TryGetInt(Metrics.Prefix + "LOC", out int _)); + Assert.IsTrue(node.TryGetInt(Metrics.Prefix + "McCabe_Complexity", out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.DistinctOperators, out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.DistinctOperands, out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.TotalOperators, out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.TotalOperands, out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.ProgramVocabulary, out int _)); + Assert.IsTrue(node.TryGetInt(Halstead.ProgramLength, out int _)); + Assert.IsTrue(node.TryGetFloat(Halstead.EstimatedProgramLength, out float _)); + Assert.IsTrue(node.TryGetFloat(Halstead.Volume, out float _)); + Assert.IsTrue(node.TryGetFloat(Halstead.Difficulty, out float _)); + Assert.IsTrue(node.TryGetFloat(Halstead.Effort, out float _)); + Assert.IsTrue(node.TryGetFloat(Halstead.TimeRequiredToProgram, out float _)); + Assert.IsTrue(node.TryGetFloat(Halstead.NumberOfDeliveredBugs, out float _)); + } + [UnityTest] public IEnumerator TestVCSMetrics() => UniTask.ToCoroutine(async () => { - GameObject go = new(); - SEECity city = go.AddComponent(); + Graph graph = await GetVCSGraphAsync(); + Assert.IsNotNull(graph); + Assert.IsTrue(graph.NodeCount > 0); + + Assert.IsTrue(graph.TryGetNode("Assets/SEE/GraphProviders/VCSGraphProvider.cs", out Node node)); - Dictionary pathGlobbing = new() { - { "Assets/SEE/**/*.cs", true } - }; + Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.LinesAdded, out int value)); + Assert.AreEqual(157, value); + } + { + Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.LinesDeleted, out int value)); + Assert.AreEqual(193, value); + } + { + Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.NumberOfDevelopers, out int value)); + Assert.AreEqual(3, value); + } + { + Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.CommitFrequency, out int value)); + Assert.AreEqual(12, value); + } + }); + + private static async Task GetVCSGraphAsync() + { + GameObject go = new(); + SEECity city = go.AddComponent(); - VCSGraphProvider provider = new() + Dictionary pathGlobbing = new() { - RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), - BaselineCommitID = "887e1fc1d6fe87ee1178822b5eeb666e62af3710", - CommitID = "5efa95913a6e894e5340f07fab26c9958b5c1096", - PathGlobbing = pathGlobbing + { "Assets/SEE/**/*.cs", true } }; - Graph loaded = await provider.ProvideAsync(new Graph(""), city); - Assert.IsNotNull(loaded); - Assert.IsTrue(loaded.NodeCount > 0); - - Assert.IsTrue(loaded.TryGetNode("Assets/SEE/GraphProviders/VCSGraphProvider.cs", out Node node)); + VCSGraphProvider provider = new() + { + RepositoryPath = new DirectoryPath(Path.GetDirectoryName(Application.dataPath)), + BaselineCommitID = "a5fe5e6a2692f41aeb8448d5114000e6f82e605e", + CommitID = "0878f91f900dc90d89c594c521ac1d3b9edd7097", + PathGlobbing = pathGlobbing + }; - // Metric from Git. - { - Assert.IsTrue(node.TryGetInt(DataModel.DG.VCS.LinesAdded, out int value)); - Assert.AreEqual(40, value); - } - }); + return await provider.ProvideAsync(new Graph(""), city); + } } } From 23b1606b5b1df53e259d5e9a49d409a394633e8b Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Thu, 13 Jun 2024 08:08:00 +0200 Subject: [PATCH 25/31] #723 Added a TODO marker. --- Assets/SEE/GraphProviders/VCSMetrics.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index 8bd2c673a5..0a55c1682c 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -28,6 +28,8 @@ public static void AddLineofCodeChurnMetric(Graph graph, string vcsPath, Commit { foreach (Node node in graph.Nodes()) { + // TODO: Why is this needed? Is it always needed on every platform? + // Can't we just use the path attribute of the node as it is? if (node.ID.Replace('\\', '/') == change.Path) { node.SetInt(Git.LinesAdded, change.LinesAdded); From 2d7c89c1f6550bbbfe7b8a2112a8deb658405a38 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 13:56:41 +0200 Subject: [PATCH 26/31] #723 Progress report and cancellation is now supported, too. --- Assets/SEE/GraphProviders/VCSGraphProvider.cs | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSGraphProvider.cs b/Assets/SEE/GraphProviders/VCSGraphProvider.cs index 8618efe323..250b210c49 100644 --- a/Assets/SEE/GraphProviders/VCSGraphProvider.cs +++ b/Assets/SEE/GraphProviders/VCSGraphProvider.cs @@ -72,13 +72,15 @@ public override GraphProviderKind GetKind() /// /// The graph into which the metrics shall be loaded /// This parameter is currently ignored. - /// This parameter is currently ignored. - /// This parameter is currently ignored. - public override async UniTask ProvideAsync(Graph graph, AbstractSEECity city, Action changePercentage = null, + /// Callback to report progress from 0 to 1. + /// Cancellation token. + public override async UniTask ProvideAsync(Graph graph, AbstractSEECity city, + Action changePercentage = null, CancellationToken token = default) { CheckArguments(city); - return await UniTask.FromResult(GetVCSGraph(PathGlobbing, RepositoryPath.Path, CommitID, BaselineCommitID)); + return await UniTask.FromResult(GetVCSGraph(PathGlobbing, RepositoryPath.Path, CommitID, BaselineCommitID, + changePercentage, token)); } /// @@ -127,8 +129,10 @@ protected void CheckArguments(AbstractSEECity city) /// The commit id where the files exist. /// The commit id of the baseline against which to gather /// the VCS metrics + /// Callback to report progress from 0 to 1. + /// Cancellation token. /// the resulting graph - private static Graph GetVCSGraph(Dictionary pathGlobbing, string repositoryPath, string commitID, string baselineCommitID) + private static Graph GetVCSGraph(Dictionary pathGlobbing, string repositoryPath, string commitID, string baselineCommitID, Action changePercentage, CancellationToken token) { string[] pathSegments = repositoryPath.Split(Path.DirectorySeparatorChar); @@ -143,11 +147,17 @@ private static Graph GetVCSGraph(Dictionary pathGlobbing, string r { LibGit2Sharp.Tree tree = repo.Lookup(commitID).Tree; // Get all files using "git ls-tree -r --name-only". - IEnumerable files = GetFilteredFiles(ListTree(tree), pathGlobbing); + List files = GetFilteredFiles(ListTree(tree), pathGlobbing); + float totalSteps = files.Count; + int currentStep = 0; // Build the graph structure. foreach (string filePath in files.Where(path => !string.IsNullOrEmpty(path))) { + if (token.IsCancellationRequested) + { + throw new OperationCanceledException(token); + } string[] filePathSegments = filePath.Split('/'); // Files in the main directory. if (filePathSegments.Length == 1) @@ -159,11 +169,14 @@ private static Graph GetVCSGraph(Dictionary pathGlobbing, string r { BuildGraphFromPath(filePath, null, null, graph, graph.GetNode(pathSegments[^1])); } + currentStep++; + changePercentage?.Invoke(currentStep / totalSteps); } AddCodeMetrics(graph, repo, commitID); ADDVCSMetrics(graph, repo, baselineCommitID, commitID); } graph.FinalizeNodeHierarchy(); + changePercentage?.Invoke(1f); return graph; } @@ -324,15 +337,16 @@ private static List ListTree(LibGit2Sharp.Tree tree) /// /// Retrieves the token stream for given file content from its repository and commit ID. /// - /// The filePath from the node. + /// The file path from the node. This must be a relative path + /// in the syntax of the repository regarding the directory separator /// The repository from which the file content is retrieved. /// The commitID where the files exist. /// The language the given text is written in. /// The token stream for the specified file and commit. - public static IEnumerable RetrieveTokens(string filePath, Repository repository, string commitID, - TokenLanguage language) + public static IEnumerable RetrieveTokens(string repositoryFilePath, Repository repository, + string commitID, TokenLanguage language) { - Blob blob = repository.Lookup($"{commitID}:{filePath}"); + Blob blob = repository.Lookup($"{commitID}:{repositoryFilePath}"); if (blob != null) { @@ -342,6 +356,7 @@ public static IEnumerable RetrieveTokens(string filePath, Repository r else { // Blob does not exist. + Debug.LogWarning($"File {repositoryFilePath} does not exist.\n"); return Enumerable.Empty(); } } @@ -360,11 +375,11 @@ private static void AddCodeMetrics(Graph graph, Repository repository, string co { if (node.Type == fileNodeType) { - string filePath = Filenames.OnCurrentPlatform(node.ID); - TokenLanguage language = TokenLanguage.FromFileExtension(Path.GetExtension(filePath).TrimStart('.')); + string repositoryFilePath = node.ID; + TokenLanguage language = TokenLanguage.FromFileExtension(Path.GetExtension(repositoryFilePath).TrimStart('.')); if (language != TokenLanguage.Plain) { - IEnumerable tokens = RetrieveTokens(filePath, repository, commitID, language); + IEnumerable tokens = RetrieveTokens(repositoryFilePath, repository, commitID, language); node.SetInt(Metrics.Prefix + "LOC", TokenMetrics.CalculateLinesOfCode(tokens)); node.SetInt(Metrics.Prefix + "McCabe_Complexity", TokenMetrics.CalculateMcCabeComplexity(tokens)); TokenMetrics.HalsteadMetrics halsteadMetrics = TokenMetrics.CalculateHalsteadMetrics(tokens); From abb675c8f32b7be0d38f11da0909e9c740a99262 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 13:57:30 +0200 Subject: [PATCH 27/31] #723 Added missing tokens CLOSE_BRACE_INSIDE and FORMAT_STRING. --- Assets/SEE/Scanner/TokenLanguage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/SEE/Scanner/TokenLanguage.cs b/Assets/SEE/Scanner/TokenLanguage.cs index 04f43a9b3a..82be4b2cdd 100644 --- a/Assets/SEE/Scanner/TokenLanguage.cs +++ b/Assets/SEE/Scanner/TokenLanguage.cs @@ -194,7 +194,7 @@ public class TokenLanguage "DIRECTIVE_TRUE", "DIRECTIVE_FALSE", "DEFINE", "UNDEF", "DIRECTIVE_IF", "ELIF", "DIRECTIVE_ELSE", "ENDIF", "LINE", "ERROR", "WARNING", "REGION", "ENDREGION", "PRAGMA", "NULLABLE", "DIRECTIVE_DEFAULT", "DIRECTIVE_HIDDEN", "DIRECTIVE_OPEN_PARENS", "DIRECTIVE_CLOSE_PARENS", "DIRECTIVE_BANG", - "DIRECTIVE_OP_EQ", "DIRECTIVE_OP_NE", "DIRECTIVE_OP_AND", "DIRECTIVE_OP_OR", "CONDITIONAL_SYMBOL" + "DIRECTIVE_OP_EQ", "DIRECTIVE_OP_NE", "DIRECTIVE_OP_AND", "DIRECTIVE_OP_OR", "CONDITIONAL_SYMBOL", }; /// @@ -224,8 +224,8 @@ public class TokenLanguage /// Set of antlr type names for CSharp separators and operators. private static readonly HashSet cSharpPunctuation = new() { - "OPEN_BRACE", "CLOSE_BRACE", "OPEN_BRACKET", - "CLOSE_BRACKET", "OPEN_PARENS", "CLOSE_PARENS", "DOT", "COMMA", "COLON", "SEMICOLON", "PLUS", "MINUS", "STAR", "DIV", + "OPEN_BRACE", "CLOSE_BRACE", "CLOSE_BRACE_INSIDE", "OPEN_BRACKET", + "CLOSE_BRACKET", "OPEN_PARENS", "CLOSE_PARENS", "DOT", "COMMA", "FORMAT_STRING", "COLON", "SEMICOLON", "PLUS", "MINUS", "STAR", "DIV", "PERCENT", "AMP", "BITWISE_OR", "CARET", "BANG", "TILDE", "ASSIGNMENT", "LT", "GT", "INTERR", "DOUBLE_COLON", "OP_COALESCING", "OP_INC", "OP_DEC", "OP_AND", "OP_OR", "OP_PTR", "OP_EQ", "OP_NE", "OP_LE", "OP_GE", "OP_ADD_ASSIGNMENT", "OP_SUB_ASSIGNMENT", "OP_MULT_ASSIGNMENT", "OP_DIV_ASSIGNMENT", "OP_MOD_ASSIGNMENT", "OP_AND_ASSIGNMENT", "OP_OR_ASSIGNMENT", From da3cbffd18b17cc776c4bde1a07ae3fea48a2928 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 13:58:16 +0200 Subject: [PATCH 28/31] #723 Fixed test case TestVCSGraphProviderAsync(). --- Assets/SEETests/TestGraphProviders.cs | 71 +++++++++++---------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index f194965b3b..da69f42aad 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -1,9 +1,7 @@ using Cysharp.Threading.Tasks; -using LibGit2Sharp; using NUnit.Framework; using SEE.DataModel.DG; using SEE.Game.City; -using SEE.Scanner; using SEE.Utils.Paths; using System.Collections; using System.Collections.Generic; @@ -133,53 +131,40 @@ public IEnumerator TestMergeDiffGraphProvider() => [Test] public async Task TestVCSGraphProviderAsync() { - Graph graph = await GetVCSGraphAsync(); - - List pathsFromGraph = new(); - foreach (GraphElement elem in graph.Elements()) - { - pathsFromGraph.Add(elem.ID); - } - const int numberOfCSFiles = 759; - Assert.AreEqual(numberOfCSFiles, pathsFromGraph.Count()); - string repositoryPath = Application.dataPath; string projectPath = repositoryPath[..repositoryPath.LastIndexOf("/")]; string projectName = Path.GetFileName(projectPath); - List actualList = new() + List expectedPaths = new() { projectName, - ".gitignore", "Assets", - "Assets/Scenes.meta", - "Assets/Scenes", - "Assets/Scenes/SampleScene.unity", - "Assets/Scenes/SampleScene.unity.meta", - "Packages", - "Packages/manifest.json", - "ProjectSettings", - "ProjectSettings/AudioManager.asset", - "ProjectSettings/ClusterInputManager.asset", - "ProjectSettings/DynamicsManager.asset", - "ProjectSettings/EditorBuildSettings.asset", - "ProjectSettings/EditorSettings.asset", - "ProjectSettings/GraphicsSettings.asset", - "ProjectSettings/InputManager.asset", - "ProjectSettings/NavMeshAreas.asset", - "ProjectSettings/Physics2DSettings.asset", - "ProjectSettings/PresetManager.asset", - "ProjectSettings/ProjectSettings.asset", - "ProjectSettings/ProjectVersion.txt", - "ProjectSettings/QualitySettings.asset", - "ProjectSettings/TagManager.asset", - "ProjectSettings/TimeManager.asset", - "ProjectSettings/UnityConnectSettings.asset", - "ProjectSettings/VFXManager.asset", - "ProjectSettings/XRSettings.asset" + "Assets/SEE", + "Assets/SEE/GraphProviders", + "Assets/SEE/GraphProviders/CSVGraphProvider.cs", + "Assets/SEE/GraphProviders/DashboardGraphProvider.cs", + "Assets/SEE/GraphProviders/FileBasedGraphProvider.cs", + "Assets/SEE/GraphProviders/GXLGraphProvider.cs", + "Assets/SEE/GraphProviders/GraphProvider.cs", + "Assets/SEE/GraphProviders/GraphProviderFactory.cs", + "Assets/SEE/GraphProviders/GraphProviderKind.cs", + "Assets/SEE/GraphProviders/JaCoCoGraphProvider.cs", + "Assets/SEE/GraphProviders/LSPGraphProvider.cs", + "Assets/SEE/GraphProviders/MergeDiffGraphProvider.cs", + "Assets/SEE/GraphProviders/PipelineGraphProvider.cs", + "Assets/SEE/GraphProviders/ReflexionGraphProvider.cs", + "Assets/SEE/GraphProviders/VCSGraphProvider.cs" }; - Assert.IsTrue(actualList.OrderByDescending(x => x).ToList().SequenceEqual(pathsFromGraph.OrderByDescending(x => x).ToList())); + Graph graph = await GetVCSGraphAsync(); + + List pathsFromGraph = new(); + foreach (GraphElement elem in graph.Elements()) + { + pathsFromGraph.Add(elem.ID); + } + Assert.AreEqual(expectedPaths.Count, pathsFromGraph.Count()); + Assert.IsTrue(expectedPaths.OrderByDescending(x => x).ToList().SequenceEqual(pathsFromGraph.OrderByDescending(x => x).ToList())); } /// @@ -243,6 +228,10 @@ public IEnumerator TestVCSMetrics() => } }); + /// + /// The graph consisting of all C# files in folder Assets/SEE/GraphProviders. + /// + /// graph consisting of all C# files in folder Assets/SEE/GraphProviders private static async Task GetVCSGraphAsync() { GameObject go = new(); @@ -250,7 +239,7 @@ private static async Task GetVCSGraphAsync() Dictionary pathGlobbing = new() { - { "Assets/SEE/**/*.cs", true } + { "Assets/SEE/GraphProviders/**/*.cs", true } }; VCSGraphProvider provider = new() From 92f6b20216ee131fa380d499d299984ae4fa544d Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 14:05:52 +0200 Subject: [PATCH 29/31] #723 Removed debugging output. --- Assets/SEETests/TestGraphProviders.cs | 1 - Assets/SEETests/TestRectanglePacker.cs | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Assets/SEETests/TestGraphProviders.cs b/Assets/SEETests/TestGraphProviders.cs index da69f42aad..0cd08afed7 100644 --- a/Assets/SEETests/TestGraphProviders.cs +++ b/Assets/SEETests/TestGraphProviders.cs @@ -183,7 +183,6 @@ public async Task TestExistenceOfTokenMetricsAsync() private static void AssertTokenMetricsExist(Node node) { - Debug.Log(node.ToString()); Assert.IsTrue(node.TryGetInt(Metrics.Prefix + "LOC", out int _)); Assert.IsTrue(node.TryGetInt(Metrics.Prefix + "McCabe_Complexity", out int _)); Assert.IsTrue(node.TryGetInt(Halstead.DistinctOperators, out int _)); diff --git a/Assets/SEETests/TestRectanglePacker.cs b/Assets/SEETests/TestRectanglePacker.cs index e80f85c46f..e324c8e7a3 100644 --- a/Assets/SEETests/TestRectanglePacker.cs +++ b/Assets/SEETests/TestRectanglePacker.cs @@ -43,7 +43,7 @@ private static bool EqualLists(IList left, IList right) } /// - /// Runs the example scenario used by Richard Wettel in his dissertation + /// Runs the example scenario used by Richard Wettel in his dissertation /// plus two additions at the end to check situations he did not cover /// in this example. See page 36 in "Software Systems as Cities" by /// Richard Wettel. @@ -51,10 +51,8 @@ private static bool EqualLists(IList left, IList right) [Test] public void TestSplit() { - Debug.Log("TestSplit\n"); - Vector2 totalSize = new Vector2(14, 12); - PTree tree = new PTree(Vector2.zero, totalSize); + PTree tree = new(Vector2.zero, totalSize); PNode A = tree.Root; Assert.That(A.Occupied, Is.False); From 00371d77852fac2fd7e091aa905adb0b5412968b Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 14:08:38 +0200 Subject: [PATCH 30/31] #723 Removed obsolete VCSMetrics enum value. --- Assets/SEE/GraphProviders/GraphProviderKind.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Assets/SEE/GraphProviders/GraphProviderKind.cs b/Assets/SEE/GraphProviders/GraphProviderKind.cs index 7433aea3de..906afa83e1 100644 --- a/Assets/SEE/GraphProviders/GraphProviderKind.cs +++ b/Assets/SEE/GraphProviders/GraphProviderKind.cs @@ -45,9 +45,5 @@ public enum GraphProviderKind /// For . /// LSP, - /// - /// For . - /// - VCSMetrics } } From 432645d744ffdcbb46c86c605502824bb0df18d5 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Fri, 14 Jun 2024 14:23:33 +0200 Subject: [PATCH 31/31] #723 Removed unnecessary directory separator replacement. The ID of a file created by a VCSGraphProvider is always the relative path in the syntax of the repository (e.g., in Git / is used as the separator). --- Assets/SEE/GraphProviders/VCSMetrics.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Assets/SEE/GraphProviders/VCSMetrics.cs b/Assets/SEE/GraphProviders/VCSMetrics.cs index 5b8b1b6d04..7a002a020b 100644 --- a/Assets/SEE/GraphProviders/VCSMetrics.cs +++ b/Assets/SEE/GraphProviders/VCSMetrics.cs @@ -66,9 +66,7 @@ private static void AddLinesOfCodeChurnMetric(Graph graph, Repository repository { foreach (Node node in graph.Nodes()) { - // TODO: Why is this needed? Is it always needed on every platform? - // Can't we just use the path attribute of the node as it is? - if (node.ID.Replace('\\', '/') == change.Path) + if (node.ID == change.Path) { node.SetInt(DataModel.DG.VCS.LinesAdded, change.LinesAdded); node.SetInt(DataModel.DG.VCS.LinesDeleted, change.LinesDeleted); @@ -119,7 +117,7 @@ private static void AddNumberOfDevelopersMetric(Graph graph, Repository reposito { foreach (Node node in graph.Nodes()) { - if (node.ID.Replace('\\', '/') == entry.Key) + if (node.ID == entry.Key) { node.SetInt(DataModel.DG.VCS.NumberOfDevelopers, entry.Value.Count); } @@ -172,7 +170,7 @@ private static void AddCommitFrequencyMetric(Graph graph, Repository repository, { foreach (Node node in graph.Nodes()) { - if (node.ID.Replace('\\', '/') == entry.Key) + if (node.ID == entry.Key) { node.SetInt(DataModel.DG.VCS.CommitFrequency, entry.Value); }