dependabot-nuget 0.315.0 → 0.316.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +15 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +47 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +60 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +151 -23
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -18
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequestExistsForSecurityUpdate.cs +15 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateDependencyNotFound.cs +9 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateIgnored.cs +10 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotFound.cs +11 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotPossible.cs +13 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +6 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ModifiedFilesTracker.cs +151 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +78 -32
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +99 -111
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +169 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +271 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/IUpdateHandler.cs +22 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +192 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +187 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +175 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +43 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ILogger.cs +17 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -9
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MarkdownListBuilder.cs +65 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ApiModel/JobTests.cs +405 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +92 -82
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +5 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +67 -1
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +445 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs +1 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +260 -20
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +30 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +69 -10
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandlerTests.cs +766 -0
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/GroupUpdateAllVersionsHandlerTests.cs +636 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandlerTests.cs +513 -0
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandlerTests.cs +806 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandlerTests.cs +589 -0
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlerSelectionTests.cs +183 -0
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +43 -0
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +2 -2
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +121 -7
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +6 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +2 -2
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +51 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MarkdownListBuilderTests.cs +42 -0
  51. metadata +26 -4
@@ -0,0 +1,187 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGetUpdater.Core.Run.ApiModel;
4
+ using NuGetUpdater.Core.Updater;
5
+
6
+ namespace NuGetUpdater.Core.Run.UpdateHandlers;
7
+
8
+ internal class RefreshSecurityUpdatePullRequestHandler : IUpdateHandler
9
+ {
10
+ public static IUpdateHandler Instance { get; } = new RefreshSecurityUpdatePullRequestHandler();
11
+
12
+ public string TagName => "update_security_pr";
13
+
14
+ public bool CanHandle(Job job)
15
+ {
16
+ if (!job.SecurityUpdatesOnly)
17
+ {
18
+ return false;
19
+ }
20
+
21
+ if (job.Dependencies.Length == 0)
22
+ {
23
+ return false;
24
+ }
25
+
26
+ return job.UpdatingAPullRequest;
27
+ }
28
+
29
+ public async Task HandleAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha, IDiscoveryWorker discoveryWorker, IAnalyzeWorker analyzeWorker, IUpdaterWorker updaterWorker, IApiHandler apiHandler, ExperimentsManager experimentsManager, ILogger logger)
30
+ {
31
+ var jobDependencies = job.Dependencies.ToHashSet(StringComparer.OrdinalIgnoreCase);
32
+ foreach (var directory in job.GetAllDirectories())
33
+ {
34
+ var discoveryResult = await discoveryWorker.RunAsync(repoContentsPath.FullName, directory);
35
+ logger.ReportDiscovery(discoveryResult);
36
+ if (discoveryResult.Error is not null)
37
+ {
38
+ await apiHandler.RecordUpdateJobError(discoveryResult.Error);
39
+ return;
40
+ }
41
+
42
+ var updatedDependencyList = RunWorker.GetUpdatedDependencyListFromDiscovery(discoveryResult);
43
+ await apiHandler.UpdateDependencyList(updatedDependencyList);
44
+ await this.ReportUpdaterStarted(apiHandler);
45
+
46
+ var updateOperationsPerformed = new List<UpdateOperationBase>();
47
+ var updatedDependencies = new List<ReportedDependency>();
48
+ var updateOperationsToPerform = RunWorker.GetUpdateOperations(discoveryResult).ToArray();
49
+ var groupedUpdateOperationsToPerform = updateOperationsToPerform
50
+ .GroupBy(o => o.Dependency.Name, StringComparer.OrdinalIgnoreCase)
51
+ .Where(g => jobDependencies.Contains(g.Key))
52
+ .ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
53
+
54
+ logger.Info($"Updating dependencies: {string.Join(", ", groupedUpdateOperationsToPerform.Select(g => g.Key).Distinct().OrderBy(d => d, StringComparer.OrdinalIgnoreCase))}");
55
+
56
+ if (groupedUpdateOperationsToPerform.Count == 0)
57
+ {
58
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependenciesRemoved(job));
59
+ continue;
60
+ }
61
+
62
+ var missingDependencies = jobDependencies
63
+ .Where(d => !groupedUpdateOperationsToPerform.ContainsKey(d))
64
+ .OrderBy(d => d, StringComparer.OrdinalIgnoreCase)
65
+ .ToImmutableArray();
66
+ if (missingDependencies.Length > 0)
67
+ {
68
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependencyRemoved(job));
69
+ continue;
70
+ }
71
+
72
+ var tracker = new ModifiedFilesTracker(repoContentsPath);
73
+ await tracker.StartTrackingAsync(discoveryResult);
74
+ foreach (var dependencyGroupToUpdate in groupedUpdateOperationsToPerform)
75
+ {
76
+ var dependencyName = dependencyGroupToUpdate.Key;
77
+ var vulnerableDependenciesToUpdate = dependencyGroupToUpdate.Value
78
+ .Select(o => (o.ProjectPath, o.Dependency, RunWorker.GetDependencyInfo(job, o.Dependency)))
79
+ .Where(set => !job.IsDependencyIgnored(set.Dependency.Name, set.Dependency.Version!))
80
+ .Where(set => set.Item3.IsVulnerable)
81
+ .ToArray();
82
+
83
+ if (vulnerableDependenciesToUpdate.Length < dependencyGroupToUpdate.Value.Length)
84
+ {
85
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithUpToDate(job));
86
+ return;
87
+ }
88
+
89
+ foreach (var (projectPath, dependency, dependencyInfo) in vulnerableDependenciesToUpdate)
90
+ {
91
+ var analysisResult = await analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
92
+ if (analysisResult.Error is not null)
93
+ {
94
+ logger.Error($"Error analyzing {dependency.Name} in {projectPath}: {analysisResult.Error.GetReport()}");
95
+ await apiHandler.RecordUpdateJobError(analysisResult.Error);
96
+ return;
97
+ }
98
+
99
+ if (!analysisResult.CanUpdate)
100
+ {
101
+ logger.Info($"No updatable version found for {dependency.Name} in {projectPath}.");
102
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithUpdateNoLongerPossible(job));
103
+ return;
104
+ }
105
+
106
+ logger.Info($"Attempting update of {dependency.Name} from {dependency.Version} to {analysisResult.UpdatedVersion} for {projectPath}.");
107
+ var projectDiscovery = discoveryResult.GetProjectDiscoveryFromPath(projectPath);
108
+ var updaterResult = await updaterWorker.RunAsync(repoContentsPath.FullName, projectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, dependency.IsTransitive);
109
+ if (updaterResult.Error is not null)
110
+ {
111
+ logger.Error($"Error updating {dependency.Name} in {projectPath}: {updaterResult.Error.GetReport()}");
112
+ await apiHandler.RecordUpdateJobError(updaterResult.Error);
113
+ continue;
114
+ }
115
+
116
+ if (updaterResult.UpdateOperations.Length == 0)
117
+ {
118
+ // nothing was done, but we may have already handled it
119
+ var alreadyHandled = updatedDependencies.Where(updated => updated.Name == dependencyName && updated.Version == analysisResult.UpdatedVersion).Any();
120
+ if (!alreadyHandled)
121
+ {
122
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithUpdateNoLongerPossible(job));
123
+ return;
124
+ }
125
+ }
126
+
127
+ var patchedUpdateOperations = RunWorker.PatchInOldVersions(updaterResult.UpdateOperations, projectDiscovery);
128
+ var updatedDependenciesForThis = patchedUpdateOperations
129
+ .Select(o => o.ToReportedDependency(projectPath, updatedDependencyList.Dependencies, analysisResult.UpdatedDependencies))
130
+ .ToArray();
131
+
132
+ updatedDependencies.AddRange(updatedDependenciesForThis);
133
+ updateOperationsPerformed.AddRange(patchedUpdateOperations);
134
+ foreach (var o in patchedUpdateOperations)
135
+ {
136
+ logger.Info($"Update operation performed: {o.GetReport()}");
137
+ }
138
+ }
139
+ }
140
+
141
+ var updatedDependencyFiles = await tracker.StopTrackingAsync();
142
+ var rawDependencies = updatedDependencies.Select(d => new Dependency(d.Name, d.Version, DependencyType.Unknown)).ToArray();
143
+ if (rawDependencies.Length > 0)
144
+ {
145
+ var commitMessage = PullRequestTextGenerator.GetPullRequestCommitMessage(job, [.. updateOperationsPerformed], null);
146
+ var prTitle = PullRequestTextGenerator.GetPullRequestTitle(job, [.. updateOperationsPerformed], null);
147
+ var prBody = PullRequestTextGenerator.GetPullRequestBody(job, [.. updateOperationsPerformed], null);
148
+
149
+ var existingPullRequest = job.GetExistingPullRequestForDependencies(rawDependencies, considerVersions: true);
150
+ if (existingPullRequest is not null)
151
+ {
152
+ await apiHandler.UpdatePullRequest(new UpdatePullRequest()
153
+ {
154
+ DependencyNames = [.. jobDependencies.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)],
155
+ DependencyGroup = null,
156
+ UpdatedDependencyFiles = [.. updatedDependencyFiles],
157
+ BaseCommitSha = baseCommitSha,
158
+ CommitMessage = commitMessage,
159
+ PrTitle = prTitle,
160
+ PrBody = prBody,
161
+ });
162
+ continue;
163
+ }
164
+ else
165
+ {
166
+ var existingPrButDifferent = job.GetExistingPullRequestForDependencies(rawDependencies, considerVersions: false);
167
+ if (existingPrButDifferent is not null)
168
+ {
169
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependenciesChanged(job));
170
+ }
171
+
172
+ await apiHandler.CreatePullRequest(new CreatePullRequest()
173
+ {
174
+ Dependencies = [.. updatedDependencies],
175
+ UpdatedDependencyFiles = [.. updatedDependencyFiles],
176
+ BaseCommitSha = baseCommitSha,
177
+ CommitMessage = commitMessage,
178
+ PrTitle = prTitle,
179
+ PrBody = prBody,
180
+ DependencyGroup = null,
181
+ });
182
+ continue;
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,175 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGetUpdater.Core.Run.ApiModel;
4
+ using NuGetUpdater.Core.Updater;
5
+
6
+ namespace NuGetUpdater.Core.Run.UpdateHandlers;
7
+
8
+ internal class RefreshVersionUpdatePullRequestHandler : IUpdateHandler
9
+ {
10
+ public static IUpdateHandler Instance { get; } = new RefreshVersionUpdatePullRequestHandler();
11
+
12
+ public string TagName => "update_version_pr";
13
+
14
+ public bool CanHandle(Job job)
15
+ {
16
+ if (job.SecurityUpdatesOnly)
17
+ {
18
+ return false;
19
+ }
20
+
21
+ if (job.Dependencies.Length == 0)
22
+ {
23
+ return false;
24
+ }
25
+
26
+ return job.UpdatingAPullRequest;
27
+ }
28
+
29
+ public async Task HandleAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha, IDiscoveryWorker discoveryWorker, IAnalyzeWorker analyzeWorker, IUpdaterWorker updaterWorker, IApiHandler apiHandler, ExperimentsManager experimentsManager, ILogger logger)
30
+ {
31
+ var jobDependencies = job.Dependencies.ToHashSet(StringComparer.OrdinalIgnoreCase);
32
+ foreach (var directory in job.GetAllDirectories())
33
+ {
34
+ var discoveryResult = await discoveryWorker.RunAsync(repoContentsPath.FullName, directory);
35
+ logger.ReportDiscovery(discoveryResult);
36
+ if (discoveryResult.Error is not null)
37
+ {
38
+ await apiHandler.RecordUpdateJobError(discoveryResult.Error);
39
+ return;
40
+ }
41
+
42
+ var updatedDependencyList = RunWorker.GetUpdatedDependencyListFromDiscovery(discoveryResult);
43
+ await apiHandler.UpdateDependencyList(updatedDependencyList);
44
+ await this.ReportUpdaterStarted(apiHandler);
45
+
46
+ var updateOperationsPerformed = new List<UpdateOperationBase>();
47
+ var updatedDependencies = new List<ReportedDependency>();
48
+ var updateOperationsToPerform = RunWorker.GetUpdateOperations(discoveryResult).ToArray();
49
+ var relevantUpdateOperationsToPerform = updateOperationsToPerform
50
+ .GroupBy(o => o.Dependency.Name, StringComparer.OrdinalIgnoreCase)
51
+ .Where(g => jobDependencies.Contains(g.Key))
52
+ .ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
53
+
54
+ if (relevantUpdateOperationsToPerform.Count == 0)
55
+ {
56
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependenciesRemoved(job));
57
+ continue;
58
+ }
59
+
60
+ var missingDependencies = jobDependencies
61
+ .Where(d => !relevantUpdateOperationsToPerform.ContainsKey(d))
62
+ .OrderBy(d => d, StringComparer.OrdinalIgnoreCase)
63
+ .ToImmutableArray();
64
+ if (missingDependencies.Length > 0)
65
+ {
66
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependencyRemoved(job));
67
+ continue;
68
+ }
69
+
70
+ logger.Info($"Updating dependencies: {string.Join(", ", relevantUpdateOperationsToPerform.Select(g => g.Key).Distinct().OrderBy(d => d, StringComparer.OrdinalIgnoreCase))}");
71
+
72
+ var tracker = new ModifiedFilesTracker(repoContentsPath);
73
+ await tracker.StartTrackingAsync(discoveryResult);
74
+ foreach (var dependencyUpdatesToPeform in relevantUpdateOperationsToPerform)
75
+ {
76
+ var dependencyName = dependencyUpdatesToPeform.Key;
77
+ var dependencyInfosToUpdate = dependencyUpdatesToPeform.Value
78
+ .Select(o => (o.ProjectPath, o.Dependency, RunWorker.GetDependencyInfo(job, o.Dependency)))
79
+ .Where(set => !job.IsDependencyIgnored(set.Dependency.Name, set.Dependency.Version!))
80
+ .ToArray();
81
+
82
+ foreach (var (projectPath, dependency, dependencyInfo) in dependencyInfosToUpdate)
83
+ {
84
+ var analysisResult = await analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
85
+ if (analysisResult.Error is not null)
86
+ {
87
+ logger.Error($"Error analyzing {dependency.Name} in {projectPath}: {analysisResult.Error.GetReport()}");
88
+ await apiHandler.RecordUpdateJobError(analysisResult.Error);
89
+ return;
90
+ }
91
+
92
+ if (!analysisResult.CanUpdate)
93
+ {
94
+ logger.Info($"No updatable version found for {dependency.Name} in {projectPath}.");
95
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithUpdateNoLongerPossible(job));
96
+ return;
97
+ }
98
+
99
+ logger.Info($"Attempting update of {dependency.Name} from {dependency.Version} to {analysisResult.UpdatedVersion} for {projectPath}.");
100
+ var projectDiscovery = discoveryResult.GetProjectDiscoveryFromPath(projectPath);
101
+ var updaterResult = await updaterWorker.RunAsync(repoContentsPath.FullName, projectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, dependency.IsTransitive);
102
+ if (updaterResult.Error is not null)
103
+ {
104
+ logger.Error($"Error updating {dependency.Name} in {projectPath}: {updaterResult.Error.GetReport()}");
105
+ await apiHandler.RecordUpdateJobError(updaterResult.Error);
106
+ continue;
107
+ }
108
+
109
+ if (updaterResult.UpdateOperations.Length == 0)
110
+ {
111
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithUpdateNoLongerPossible(job));
112
+ return;
113
+ }
114
+
115
+ var patchedUpdateOperations = RunWorker.PatchInOldVersions(updaterResult.UpdateOperations, projectDiscovery);
116
+ var updatedDependenciesForThis = patchedUpdateOperations
117
+ .Select(o => o.ToReportedDependency(projectPath, updatedDependencyList.Dependencies, analysisResult.UpdatedDependencies))
118
+ .ToArray();
119
+
120
+ updatedDependencies.AddRange(updatedDependenciesForThis);
121
+ updateOperationsPerformed.AddRange(patchedUpdateOperations);
122
+ foreach (var o in patchedUpdateOperations)
123
+ {
124
+ logger.Info($"Update operation performed: {o.GetReport()}");
125
+ }
126
+ }
127
+ }
128
+
129
+ var updatedDependencyFiles = await tracker.StopTrackingAsync();
130
+ var rawDependencies = updatedDependencies.Select(d => new Dependency(d.Name, d.Version, DependencyType.Unknown)).ToArray();
131
+ if (rawDependencies.Length > 0)
132
+ {
133
+ var commitMessage = PullRequestTextGenerator.GetPullRequestCommitMessage(job, [.. updateOperationsPerformed], null);
134
+ var prTitle = PullRequestTextGenerator.GetPullRequestTitle(job, [.. updateOperationsPerformed], null);
135
+ var prBody = PullRequestTextGenerator.GetPullRequestBody(job, [.. updateOperationsPerformed], null);
136
+
137
+ var existingPullRequest = job.GetExistingPullRequestForDependencies(rawDependencies, considerVersions: true);
138
+ if (existingPullRequest is not null)
139
+ {
140
+ await apiHandler.UpdatePullRequest(new UpdatePullRequest()
141
+ {
142
+ DependencyNames = [.. jobDependencies.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)],
143
+ DependencyGroup = null,
144
+ UpdatedDependencyFiles = [.. updatedDependencyFiles],
145
+ BaseCommitSha = baseCommitSha,
146
+ CommitMessage = commitMessage,
147
+ PrTitle = prTitle,
148
+ PrBody = prBody,
149
+ });
150
+ continue;
151
+ }
152
+ else
153
+ {
154
+ var existingPrButDifferent = job.GetExistingPullRequestForDependencies(rawDependencies, considerVersions: false);
155
+ if (existingPrButDifferent is not null)
156
+ {
157
+ await apiHandler.ClosePullRequest(ClosePullRequest.WithDependenciesChanged(job));
158
+ }
159
+
160
+ await apiHandler.CreatePullRequest(new CreatePullRequest()
161
+ {
162
+ Dependencies = [.. updatedDependencies],
163
+ UpdatedDependencyFiles = [.. updatedDependencyFiles],
164
+ BaseCommitSha = baseCommitSha,
165
+ CommitMessage = commitMessage,
166
+ PrTitle = prTitle,
167
+ PrBody = prBody,
168
+ DependencyGroup = null,
169
+ });
170
+ continue;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
@@ -4,6 +4,7 @@ using System.Text.Json.Serialization;
4
4
 
5
5
  using NuGet.Versioning;
6
6
 
7
+ using NuGetUpdater.Core.Run.ApiModel;
7
8
  using NuGetUpdater.Core.Utilities;
8
9
 
9
10
 
@@ -16,11 +17,38 @@ public abstract record UpdateOperationBase
16
17
  {
17
18
  public abstract string Type { get; }
18
19
  public required string DependencyName { get; init; }
20
+ public NuGetVersion? OldVersion { get; init; } = null;
19
21
  public required NuGetVersion NewVersion { get; init; }
20
22
  public required ImmutableArray<string> UpdatedFiles { get; init; }
21
23
 
22
24
  public abstract string GetReport();
23
25
 
26
+ public ReportedDependency ToReportedDependency(string projectPath, IEnumerable<ReportedDependency> previouslyReportedDependencies, IEnumerable<Dependency> updatedDependencies)
27
+ {
28
+ var updatedFilesSet = UpdatedFiles.ToHashSet(StringComparer.OrdinalIgnoreCase);
29
+ var previousDependency = previouslyReportedDependencies
30
+ .SingleOrDefault(d => d.Name.Equals(DependencyName, StringComparison.OrdinalIgnoreCase) && PathComparer.Instance.Equals(d.Requirements.Single().File, projectPath));
31
+ return new ReportedDependency()
32
+ {
33
+ Name = DependencyName,
34
+ Version = NewVersion.ToString(),
35
+ Requirements = [
36
+ new()
37
+ {
38
+ File = projectPath,
39
+ Requirement = NewVersion.ToString(),
40
+ Groups = previousDependency?.Requirements.FirstOrDefault()?.Groups ?? [],
41
+ Source = new()
42
+ {
43
+ SourceUrl = updatedDependencies.FirstOrDefault(d => d.Name.Equals(DependencyName, StringComparison.OrdinalIgnoreCase))?.InfoUrl,
44
+ }
45
+ }
46
+ ],
47
+ PreviousVersion = previousDependency?.Version,
48
+ PreviousRequirements = previousDependency?.Requirements,
49
+ };
50
+ }
51
+
24
52
  internal static string GenerateUpdateOperationReport(IEnumerable<UpdateOperationBase> updateOperations)
25
53
  {
26
54
  var updateMessages = updateOperations.Select(u => u.GetReport()).ToImmutableArray();
@@ -36,7 +64,7 @@ public abstract record UpdateOperationBase
36
64
  internal static ImmutableArray<UpdateOperationBase> NormalizeUpdateOperationCollection(string repoRootPath, IEnumerable<UpdateOperationBase> updateOperations)
37
65
  {
38
66
  var groupedByKindWithCombinedFiles = updateOperations
39
- .GroupBy(u => (u.GetType(), u.DependencyName, u.NewVersion))
67
+ .GroupBy(u => (u.GetType(), u.DependencyName, u.OldVersion, u.NewVersion))
40
68
  .Select(g =>
41
69
  {
42
70
  if (g.Key.Item1 == typeof(DirectUpdate))
@@ -44,6 +72,7 @@ public abstract record UpdateOperationBase
44
72
  return new DirectUpdate()
45
73
  {
46
74
  DependencyName = g.Key.DependencyName,
75
+ OldVersion = g.Key.OldVersion,
47
76
  NewVersion = g.Key.NewVersion,
48
77
  UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
49
78
  } as UpdateOperationBase;
@@ -53,6 +82,7 @@ public abstract record UpdateOperationBase
53
82
  return new PinnedUpdate()
54
83
  {
55
84
  DependencyName = g.Key.DependencyName,
85
+ OldVersion = g.Key.OldVersion,
56
86
  NewVersion = g.Key.NewVersion,
57
87
  UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
58
88
  };
@@ -63,6 +93,7 @@ public abstract record UpdateOperationBase
63
93
  return new ParentUpdate()
64
94
  {
65
95
  DependencyName = g.Key.DependencyName,
96
+ OldVersion = g.Key.OldVersion,
66
97
  NewVersion = g.Key.NewVersion,
67
98
  UpdatedFiles = [.. g.SelectMany(u => u.UpdatedFiles)],
68
99
  ParentDependencyName = parentUpdate.ParentDependencyName,
@@ -82,6 +113,7 @@ public abstract record UpdateOperationBase
82
113
  var ordered = uniqueUpdateOperations
83
114
  .OrderBy(u => u.GetType().Name)
84
115
  .ThenBy(u => u.DependencyName)
116
+ .ThenBy(u => u.OldVersion)
85
117
  .ThenBy(u => u.NewVersion)
86
118
  .ThenBy(u => u.UpdatedFiles.Length)
87
119
  .ThenBy(u => string.Join(",", u.UpdatedFiles))
@@ -95,6 +127,7 @@ public abstract record UpdateOperationBase
95
127
  {
96
128
  var hash = new HashCode();
97
129
  hash.Add(DependencyName);
130
+ hash.Add(OldVersion);
98
131
  hash.Add(NewVersion);
99
132
  hash.Add(UpdatedFiles.Length);
100
133
  for (int i = 0; i < UpdatedFiles.Length; i++)
@@ -111,7 +144,14 @@ public abstract record UpdateOperationBase
111
144
  public record DirectUpdate : UpdateOperationBase
112
145
  {
113
146
  public override string Type => nameof(DirectUpdate);
114
- public override string GetReport() => $"Updated {DependencyName} to {NewVersion} in {string.Join(", ", UpdatedFiles)}";
147
+ public override string GetReport()
148
+ {
149
+ var fromText = OldVersion is null
150
+ ? string.Empty
151
+ : $"from {OldVersion} ";
152
+ return $"Updated {DependencyName} {fromText}to {NewVersion} in {string.Join(", ", UpdatedFiles)}";
153
+ }
154
+
115
155
  public sealed override string ToString() => GetString();
116
156
  }
117
157
 
@@ -185,6 +225,7 @@ public class UpdateOperationBaseComparer : IEqualityComparer<UpdateOperationBase
185
225
  }
186
226
 
187
227
  if (x.DependencyName != y.DependencyName ||
228
+ x.OldVersion != y.OldVersion ||
188
229
  x.NewVersion != y.NewVersion ||
189
230
  !x.UpdatedFiles.SequenceEqual(y.UpdatedFiles))
190
231
  {
@@ -1,3 +1,8 @@
1
+ using System.Text.Json;
2
+
3
+ using NuGetUpdater.Core.Analyze;
4
+ using NuGetUpdater.Core.Discover;
5
+
1
6
  namespace NuGetUpdater.Core;
2
7
 
3
8
  public interface ILogger
@@ -11,6 +16,18 @@ public static class LoggerExtensions
11
16
  public static void Warn(this ILogger logger, string message) => logger.LogWithLevel("WARN", message);
12
17
  public static void Error(this ILogger logger, string message) => logger.LogWithLevel("ERROR", message);
13
18
 
19
+ public static void ReportAnalysis(this ILogger logger, AnalysisResult analysisResult)
20
+ {
21
+ logger.Info("Analysis JSON content:");
22
+ logger.Info(JsonSerializer.Serialize(analysisResult, AnalyzeWorker.SerializerOptions));
23
+ }
24
+
25
+ public static void ReportDiscovery(this ILogger logger, WorkspaceDiscoveryResult discoveryResult)
26
+ {
27
+ logger.Info("Discovery JSON content:");
28
+ logger.Info(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
29
+ }
30
+
14
31
  private static void LogWithLevel(this ILogger logger, string level, string message) => logger.LogRaw($"{GetCurrentTimestamp()} {level} {message}");
15
32
  private static string GetCurrentTimestamp() => DateTime.UtcNow.ToString("yyyy/MM/dd HH:mm:ss");
16
33
  }
@@ -968,7 +968,7 @@ internal static partial class MSBuildHelper
968
968
  ThrowOnMissingPackages(output);
969
969
  ThrowOnUpdateNotPossible(output);
970
970
  ThrowOnRateLimitExceeded(output);
971
- ThrowOnServiceUnavailable(output);
971
+ ThrowOnBadResponse(output);
972
972
  ThrowOnUnparseableFile(output);
973
973
  }
974
974
 
@@ -1001,16 +1001,20 @@ internal static partial class MSBuildHelper
1001
1001
  }
1002
1002
  }
1003
1003
 
1004
- private static void ThrowOnServiceUnavailable(string stdout)
1004
+ private static void ThrowOnBadResponse(string stdout)
1005
1005
  {
1006
- var serviceUnavailableMessageSnippets = new string[]
1006
+ var patterns = new[]
1007
1007
  {
1008
- "503 (Service Unavailable)",
1009
- "Response status code does not indicate success: 503",
1008
+ new Regex(@"500 \(Internal Server Error\)"),
1009
+ new Regex(@"503 \(Service Unavailable\)"),
1010
+ new Regex(@"Response status code does not indicate success: 50\d"),
1011
+ new Regex(@"The file is not a valid nupkg"),
1012
+ new Regex(@"The response ended prematurely\. \(ResponseEnded\)"),
1013
+ new Regex(@"The content at '.*' is not valid XML\."),
1010
1014
  };
1011
- if (serviceUnavailableMessageSnippets.Any(stdout.Contains))
1015
+ if (patterns.Any(p => p.IsMatch(stdout)))
1012
1016
  {
1013
- throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.ServiceUnavailable);
1017
+ throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.InternalServerError);
1014
1018
  }
1015
1019
  }
1016
1020
 
@@ -1030,11 +1034,12 @@ internal static partial class MSBuildHelper
1030
1034
  new Regex(@"Package '(?<PackageName>[^']*)' is not found on source '(?<PackageSource>[^$\r\n]*)'\."),
1031
1035
  new Regex(@"Unable to find package (?<PackageName>[^ ]+)\. No packages exist with this id in source\(s\): (?<PackageSource>.*)$", RegexOptions.Multiline),
1032
1036
  new Regex(@"Unable to find package (?<PackageName>[^ ]+) with version \((?<PackageVersion>[^)]+)\)"),
1037
+ new Regex(@"Unable to find package '(?<PackageName>[^ ]+)'\."),
1033
1038
  new Regex(@"Could not resolve SDK ""(?<PackageName>[^ ]+)""\."),
1034
1039
  new Regex(@"Failed to fetch results from V2 feed at '.*FindPackagesById\(\)\?id='(?<PackageName>[^']+)'&semVerLevel=2\.0\.0' with following message : Response status code does not indicate success: 404\."),
1035
1040
  };
1036
- var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success);
1037
- if (matches.Any())
1041
+ var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success).ToArray();
1042
+ if (matches.Length > 0)
1038
1043
  {
1039
1044
  var packages = matches.Select(m =>
1040
1045
  {
@@ -1075,6 +1080,7 @@ internal static partial class MSBuildHelper
1075
1080
  var patterns = new[]
1076
1081
  {
1077
1082
  new Regex(@"\nAn error occurred while reading file '(?<FilePath>[^']+)': (?<Message>[^\n]*)\n"),
1083
+ new Regex(@"NuGet\.Config is not valid XML\. Path: '(?<FilePath>[^']+)'\.\n\s*(?<Message>[^\n]*)(\n|$)"),
1078
1084
  };
1079
1085
  var match = patterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
1080
1086
  if (match is not null)
@@ -0,0 +1,65 @@
1
+ namespace NuGetUpdater.Core.Utilities;
2
+
3
+ public class MarkdownListBuilder
4
+ {
5
+ public static string FromObject(object obj)
6
+ {
7
+ return string.Join(Environment.NewLine, LinesFromObject(obj));
8
+ }
9
+
10
+ private static string[] LinesFromObject(object obj)
11
+ {
12
+ var lines = new List<string>();
13
+ switch (obj)
14
+ {
15
+ case IDictionary<string, object> dict:
16
+ // key1: value1
17
+ // key2: value2
18
+ foreach (var (key, value) in dict)
19
+ {
20
+ if (key == "error-backtrace")
21
+ {
22
+ continue;
23
+ }
24
+
25
+ var childLines = LinesFromObject(value);
26
+ if (childLines.Length == 1)
27
+ {
28
+ // display inline
29
+ lines.Add($"- {key}: {childLines[0]}");
30
+ }
31
+ else
32
+ {
33
+ // display in sub-list
34
+ lines.Add($"- {key}:");
35
+ foreach (var childLine in childLines)
36
+ {
37
+ lines.Add($" {childLine}");
38
+ }
39
+ }
40
+ }
41
+ break;
42
+ case IEnumerable<object> values:
43
+ // - value1
44
+ // - value2
45
+ foreach (var value in values)
46
+ {
47
+ var valueLines = LinesFromObject(value);
48
+ lines.Add($"- {valueLines[0]}");
49
+ foreach (var valueLine in valueLines.Skip(1))
50
+ {
51
+ lines.Add($" {valueLine}");
52
+ }
53
+ }
54
+ break;
55
+ case bool b:
56
+ lines.Add(b.ToString().ToLowerInvariant());
57
+ break;
58
+ default:
59
+ lines.Add(obj.ToString()!);
60
+ break;
61
+ }
62
+
63
+ return [.. lines];
64
+ }
65
+ }