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.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ClosePullRequest.cs +15 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +47 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +60 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +151 -23
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +4 -18
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequestExistsForSecurityUpdate.cs +15 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateDependencyNotFound.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateIgnored.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotFound.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/SecurityUpdateNotPossible.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatePullRequest.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ModifiedFilesTracker.cs +151 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestTextGenerator.cs +78 -32
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +99 -111
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +169 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +271 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/IUpdateHandler.cs +22 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +192 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +187 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +175 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationBase.cs +43 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ILogger.cs +17 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +15 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MarkdownListBuilder.cs +65 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ApiModel/JobTests.cs +405 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs +92 -82
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +5 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +67 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +445 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestTextTests.cs +260 -20
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +30 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +69 -10
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandlerTests.cs +766 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/GroupUpdateAllVersionsHandlerTests.cs +636 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandlerTests.cs +513 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandlerTests.cs +806 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandlerTests.cs +589 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlerSelectionTests.cs +183 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlersTestsBase.cs +43 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateOperationBaseTests.cs +121 -7
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +51 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MarkdownListBuilderTests.cs +42 -0
- metadata +26 -4
@@ -0,0 +1,151 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
|
3
|
+
using NuGetUpdater.Core.Discover;
|
4
|
+
using NuGetUpdater.Core.Run.ApiModel;
|
5
|
+
using NuGetUpdater.Core.Utilities;
|
6
|
+
|
7
|
+
using static NuGetUpdater.Core.Utilities.EOLHandling;
|
8
|
+
|
9
|
+
namespace NuGetUpdater.Core.Run;
|
10
|
+
|
11
|
+
public class ModifiedFilesTracker
|
12
|
+
{
|
13
|
+
public readonly DirectoryInfo RepoContentsPath;
|
14
|
+
private WorkspaceDiscoveryResult? _currentDiscoveryResult = null;
|
15
|
+
|
16
|
+
private readonly Dictionary<string, string> _originalDependencyFileContents = [];
|
17
|
+
private readonly Dictionary<string, EOLType> _originalDependencyFileEOFs = [];
|
18
|
+
private readonly Dictionary<string, bool> _originalDependencyFileBOMs = [];
|
19
|
+
private string[] _nonProjectFiles = [];
|
20
|
+
|
21
|
+
public IReadOnlyDictionary<string, string> OriginalDependencyFileContents => _originalDependencyFileContents;
|
22
|
+
//public IReadOnlyDictionary<string, EOLType> OriginalDependencyFileEOFs => _originalDependencyFileEOFs;
|
23
|
+
public IReadOnlyDictionary<string, bool> OriginalDependencyFileBOMs => _originalDependencyFileBOMs;
|
24
|
+
|
25
|
+
public ModifiedFilesTracker(DirectoryInfo repoContentsPath)
|
26
|
+
{
|
27
|
+
RepoContentsPath = repoContentsPath;
|
28
|
+
}
|
29
|
+
|
30
|
+
public async Task StartTrackingAsync(WorkspaceDiscoveryResult discoveryResult)
|
31
|
+
{
|
32
|
+
if (_currentDiscoveryResult is not null)
|
33
|
+
{
|
34
|
+
throw new InvalidOperationException("Already tracking modified files.");
|
35
|
+
}
|
36
|
+
|
37
|
+
_currentDiscoveryResult = discoveryResult;
|
38
|
+
|
39
|
+
// track original contents for later handling
|
40
|
+
async Task TrackOriginalContentsAsync(string directory, string fileName)
|
41
|
+
{
|
42
|
+
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
43
|
+
var localFullPath = Path.Join(RepoContentsPath.FullName, repoFullPath);
|
44
|
+
var content = await File.ReadAllTextAsync(localFullPath);
|
45
|
+
var rawContent = await File.ReadAllBytesAsync(localFullPath);
|
46
|
+
_originalDependencyFileContents[repoFullPath] = content;
|
47
|
+
_originalDependencyFileEOFs[repoFullPath] = content.GetPredominantEOL();
|
48
|
+
_originalDependencyFileBOMs[repoFullPath] = rawContent.HasBOM();
|
49
|
+
}
|
50
|
+
|
51
|
+
foreach (var project in _currentDiscoveryResult.Projects)
|
52
|
+
{
|
53
|
+
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
54
|
+
await TrackOriginalContentsAsync(_currentDiscoveryResult.Path, project.FilePath);
|
55
|
+
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
56
|
+
{
|
57
|
+
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
58
|
+
await TrackOriginalContentsAsync(_currentDiscoveryResult.Path, extraFilePath);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
_nonProjectFiles = new[]
|
63
|
+
{
|
64
|
+
_currentDiscoveryResult.GlobalJson?.FilePath,
|
65
|
+
_currentDiscoveryResult.DotNetToolsJson?.FilePath,
|
66
|
+
}.Where(f => f is not null).Cast<string>().ToArray();
|
67
|
+
foreach (var nonProjectFile in _nonProjectFiles)
|
68
|
+
{
|
69
|
+
await TrackOriginalContentsAsync(_currentDiscoveryResult.Path, nonProjectFile);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
public async Task<ImmutableArray<DependencyFile>> StopTrackingAsync()
|
74
|
+
{
|
75
|
+
if (_currentDiscoveryResult is null)
|
76
|
+
{
|
77
|
+
throw new InvalidOperationException("No discovery result to track.");
|
78
|
+
}
|
79
|
+
|
80
|
+
var updatedDependencyFiles = new Dictionary<string, DependencyFile>();
|
81
|
+
async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
|
82
|
+
{
|
83
|
+
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
84
|
+
var localFullPath = Path.GetFullPath(Path.Join(RepoContentsPath.FullName, repoFullPath));
|
85
|
+
var originalContent = _originalDependencyFileContents[repoFullPath];
|
86
|
+
var updatedContent = await File.ReadAllTextAsync(localFullPath);
|
87
|
+
|
88
|
+
updatedContent = updatedContent.SetEOL(_originalDependencyFileEOFs[repoFullPath]);
|
89
|
+
var updatedRawContent = updatedContent.SetBOM(_originalDependencyFileBOMs[repoFullPath]);
|
90
|
+
await File.WriteAllBytesAsync(localFullPath, updatedRawContent);
|
91
|
+
|
92
|
+
if (updatedContent != originalContent)
|
93
|
+
{
|
94
|
+
var reportedContent = updatedContent;
|
95
|
+
var encoding = "utf-8";
|
96
|
+
if (_originalDependencyFileBOMs[repoFullPath])
|
97
|
+
{
|
98
|
+
reportedContent = Convert.ToBase64String(updatedRawContent);
|
99
|
+
encoding = "base64";
|
100
|
+
}
|
101
|
+
|
102
|
+
updatedDependencyFiles[localFullPath] = new DependencyFile()
|
103
|
+
{
|
104
|
+
Name = Path.GetFileName(repoFullPath),
|
105
|
+
Directory = Path.GetDirectoryName(repoFullPath)!.NormalizePathToUnix(),
|
106
|
+
Content = reportedContent,
|
107
|
+
ContentEncoding = encoding,
|
108
|
+
};
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
foreach (var project in _currentDiscoveryResult.Projects)
|
113
|
+
{
|
114
|
+
await AddUpdatedFileIfDifferentAsync(_currentDiscoveryResult.Path, project.FilePath);
|
115
|
+
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
116
|
+
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
117
|
+
{
|
118
|
+
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
119
|
+
await AddUpdatedFileIfDifferentAsync(_currentDiscoveryResult.Path, extraFilePath);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
foreach (var nonProjectFile in _nonProjectFiles)
|
124
|
+
{
|
125
|
+
await AddUpdatedFileIfDifferentAsync(_currentDiscoveryResult.Path, nonProjectFile);
|
126
|
+
}
|
127
|
+
|
128
|
+
_currentDiscoveryResult = null;
|
129
|
+
|
130
|
+
var updatedDependencyFileList = updatedDependencyFiles
|
131
|
+
.OrderBy(kvp => kvp.Key)
|
132
|
+
.Select(kvp => kvp.Value)
|
133
|
+
.ToImmutableArray();
|
134
|
+
return updatedDependencyFileList;
|
135
|
+
}
|
136
|
+
|
137
|
+
public static ImmutableArray<DependencyFile> MergeUpdatedFileSet(ImmutableArray<DependencyFile> setA, ImmutableArray<DependencyFile> setB)
|
138
|
+
{
|
139
|
+
static string GetFullName(DependencyFile df) => Path.Join(df.Directory, df.Name).NormalizePathToUnix();
|
140
|
+
var finalSet = setA.ToDictionary(GetFullName, df => df);
|
141
|
+
foreach (var dependencyFile in setB)
|
142
|
+
{
|
143
|
+
finalSet[GetFullName(dependencyFile)] = dependencyFile;
|
144
|
+
}
|
145
|
+
|
146
|
+
return finalSet
|
147
|
+
.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase)
|
148
|
+
.Select(kvp => kvp.Value)
|
149
|
+
.ToImmutableArray();
|
150
|
+
}
|
151
|
+
}
|
@@ -1,10 +1,12 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
-
|
3
|
-
using
|
2
|
+
using System.Text;
|
3
|
+
using System.Text.RegularExpressions;
|
4
4
|
|
5
5
|
using NuGetUpdater.Core.Run.ApiModel;
|
6
6
|
using NuGetUpdater.Core.Updater;
|
7
7
|
|
8
|
+
using DependencySet = (string Name, (NuGet.Versioning.NuGetVersion? OldVersion, NuGet.Versioning.NuGetVersion NewVersion)[] Versions);
|
9
|
+
|
8
10
|
namespace NuGetUpdater.Core.Run;
|
9
11
|
|
10
12
|
public class PullRequestTextGenerator
|
@@ -13,20 +15,58 @@ public class PullRequestTextGenerator
|
|
13
15
|
|
14
16
|
public static string GetPullRequestTitle(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
|
15
17
|
{
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
var updatedPartTitles = dependencySets
|
22
|
-
.Select(d => $"{d.Name} to {string.Join(", ", d.Versions.Select(v => v.ToString()))}")
|
23
|
-
.ToArray();
|
24
|
-
var title = $"{job.CommitMessageOptions?.Prefix}Update {string.Join("; ", updatedPartTitles)}";
|
18
|
+
var shortTitle = GetPullRequestShortTitle(job, updateOperationsPerformed, dependencyGroupName);
|
19
|
+
var titlePrefix = GetPullRequestTitlePrefix(job);
|
20
|
+
var fullTitle = $"{titlePrefix}{shortTitle}";
|
21
|
+
return fullTitle;
|
22
|
+
}
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
private static string GetPullRequestTitlePrefix(Job job)
|
25
|
+
{
|
26
|
+
if (string.IsNullOrEmpty(job.CommitMessageOptions?.Prefix))
|
28
27
|
{
|
29
|
-
|
28
|
+
return string.Empty;
|
29
|
+
}
|
30
|
+
|
31
|
+
var prefix = job.CommitMessageOptions?.Prefix ?? string.Empty;
|
32
|
+
if (Regex.IsMatch(prefix, @"[a-z0-9\)\]]$", RegexOptions.IgnoreCase))
|
33
|
+
{
|
34
|
+
prefix += ":";
|
35
|
+
}
|
36
|
+
|
37
|
+
if (!prefix.EndsWith(" "))
|
38
|
+
{
|
39
|
+
prefix += " ";
|
40
|
+
}
|
41
|
+
|
42
|
+
return prefix;
|
43
|
+
}
|
44
|
+
|
45
|
+
private static string GetPullRequestShortTitle(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
|
46
|
+
{
|
47
|
+
string title;
|
48
|
+
var dependencySets = GetDependencySets(updateOperationsPerformed);
|
49
|
+
if (dependencyGroupName is not null)
|
50
|
+
{
|
51
|
+
title = $"Bump the {dependencyGroupName} group with {dependencySets.Length} update{(dependencySets.Length > 1 ? "s" : "")}";
|
52
|
+
}
|
53
|
+
else
|
54
|
+
{
|
55
|
+
if (dependencySets.Length == 1)
|
56
|
+
{
|
57
|
+
title = GetDependencySetBumpText(dependencySets[0], isCommitMessageDetail: false);
|
58
|
+
}
|
59
|
+
else
|
60
|
+
{
|
61
|
+
var dependencyNames = dependencySets.Select(d => d.Name).Distinct().OrderBy(n => n).ToArray();
|
62
|
+
title = $"Bump {string.Join(", ", dependencyNames.Take(dependencyNames.Length - 1))} and {dependencyNames[^1]}";
|
63
|
+
|
64
|
+
// don't let the title get too long
|
65
|
+
if (title.Length > MaxTitleLength && dependencyNames.Length >= 3)
|
66
|
+
{
|
67
|
+
title = $"Bump {dependencyNames[0]} and {dependencyNames.Length - 1} others";
|
68
|
+
}
|
69
|
+
}
|
30
70
|
}
|
31
71
|
|
32
72
|
return title;
|
@@ -34,28 +74,33 @@ public class PullRequestTextGenerator
|
|
34
74
|
|
35
75
|
public static string GetPullRequestCommitMessage(Job job, ImmutableArray<UpdateOperationBase> updateOperationsPerformed, string? dependencyGroupName)
|
36
76
|
{
|
37
|
-
|
38
|
-
|
39
|
-
// if multiple packages are updated, result looks like:
|
40
|
-
// Update:
|
41
|
-
// - Package.A to 1.0.0
|
42
|
-
// - Package.B to 2.0.0
|
77
|
+
var sb = new StringBuilder();
|
78
|
+
sb.AppendLine(GetPullRequestTitle(job, updateOperationsPerformed, dependencyGroupName));
|
43
79
|
var dependencySets = GetDependencySets(updateOperationsPerformed);
|
44
|
-
if (dependencySets.Length
|
80
|
+
if (dependencySets.Length > 1 ||
|
81
|
+
dependencyGroupName is not null)
|
45
82
|
{
|
46
|
-
|
47
|
-
|
48
|
-
|
83
|
+
// multiple updates performed, enumerate them
|
84
|
+
sb.AppendLine();
|
85
|
+
foreach (var dependencySet in dependencySets)
|
86
|
+
{
|
87
|
+
sb.AppendLine(GetDependencySetBumpText(dependencySet, isCommitMessageDetail: true));
|
88
|
+
}
|
49
89
|
}
|
50
90
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
91
|
+
return sb.ToString().Replace("\r", "").TrimEnd();
|
92
|
+
}
|
93
|
+
|
94
|
+
private static string GetDependencySetBumpText(DependencySet dependencySet, bool isCommitMessageDetail)
|
95
|
+
{
|
96
|
+
var bumpSuffix = isCommitMessageDetail ? "s" : string.Empty; // "Bumps" for commit message details, "Bump" otherwise
|
97
|
+
var fromText = dependencySet.Versions.Length == 1 && dependencySet.Versions[0].OldVersion is not null
|
98
|
+
? $"from {dependencySet.Versions[0].OldVersion} "
|
99
|
+
: string.Empty;
|
100
|
+
return $"Bump{bumpSuffix} {dependencySet.Name} {fromText}to {string.Join(", ", dependencySet.Versions.Select(v => v.NewVersion.ToString()))}";
|
56
101
|
}
|
57
102
|
|
58
|
-
private static
|
103
|
+
private static DependencySet[] GetDependencySets(ImmutableArray<UpdateOperationBase> updateOperationsPerformed)
|
59
104
|
{
|
60
105
|
var dependencySets = updateOperationsPerformed
|
61
106
|
.GroupBy(d => d.DependencyName, StringComparer.OrdinalIgnoreCase)
|
@@ -64,8 +109,9 @@ public class PullRequestTextGenerator
|
|
64
109
|
{
|
65
110
|
var name = g.Key;
|
66
111
|
var versions = g
|
67
|
-
.
|
68
|
-
.
|
112
|
+
.OrderBy(d => d.OldVersion)
|
113
|
+
.ThenBy(d => d.NewVersion)
|
114
|
+
.Select(d => (d.OldVersion, d.NewVersion))
|
69
115
|
.ToArray();
|
70
116
|
return (name, versions);
|
71
117
|
})
|
@@ -1,4 +1,5 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
+
using System.IO;
|
2
3
|
using System.Text;
|
3
4
|
using System.Text.Json;
|
4
5
|
using System.Text.Json.Serialization;
|
@@ -10,11 +11,10 @@ using NuGet.Versioning;
|
|
10
11
|
using NuGetUpdater.Core.Analyze;
|
11
12
|
using NuGetUpdater.Core.Discover;
|
12
13
|
using NuGetUpdater.Core.Run.ApiModel;
|
14
|
+
using NuGetUpdater.Core.Run.UpdateHandlers;
|
13
15
|
using NuGetUpdater.Core.Updater;
|
14
16
|
using NuGetUpdater.Core.Utilities;
|
15
17
|
|
16
|
-
using static NuGetUpdater.Core.Utilities.EOLHandling;
|
17
|
-
|
18
18
|
namespace NuGetUpdater.Core.Run;
|
19
19
|
|
20
20
|
public class RunWorker
|
@@ -47,17 +47,74 @@ public class RunWorker
|
|
47
47
|
{
|
48
48
|
var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
|
49
49
|
var jobWrapper = Deserialize(jobFileContent);
|
50
|
-
var
|
51
|
-
var
|
52
|
-
|
50
|
+
var experimentsManager = ExperimentsManager.GetExperimentsManager(jobWrapper.Job.Experiments);
|
51
|
+
var result = await RunAsync(jobWrapper.Job, repoContentsPath, baseCommitSha, experimentsManager);
|
52
|
+
if (experimentsManager.UseLegacyUpdateHandler)
|
53
|
+
{
|
54
|
+
// only the legacy handler writes this file
|
55
|
+
var resultJson = JsonSerializer.Serialize(result, SerializerOptions);
|
56
|
+
await File.WriteAllTextAsync(outputFilePath.FullName, resultJson);
|
57
|
+
}
|
53
58
|
}
|
54
59
|
|
55
|
-
public Task<RunResult> RunAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
|
60
|
+
public async Task<RunResult> RunAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha, ExperimentsManager experimentsManager)
|
56
61
|
{
|
57
|
-
|
62
|
+
RunResult result;
|
63
|
+
if (experimentsManager.UseLegacyUpdateHandler)
|
64
|
+
{
|
65
|
+
result = await RunWithErrorHandlingAsync(job, repoContentsPath, baseCommitSha, experimentsManager);
|
66
|
+
}
|
67
|
+
else
|
68
|
+
{
|
69
|
+
await RunScenarioHandlersWithErrorHandlingAsync(job, repoContentsPath, baseCommitSha, experimentsManager);
|
70
|
+
|
71
|
+
// the group updater doesn't return this, so we provide an empty object
|
72
|
+
result = new RunResult()
|
73
|
+
{
|
74
|
+
Base64DependencyFiles = [],
|
75
|
+
BaseCommitSha = baseCommitSha,
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
return result;
|
58
80
|
}
|
59
81
|
|
60
|
-
private
|
82
|
+
private static readonly ImmutableArray<IUpdateHandler> UpdateHandlers =
|
83
|
+
[
|
84
|
+
GroupUpdateAllVersionsHandler.Instance,
|
85
|
+
RefreshGroupUpdatePullRequestHandler.Instance,
|
86
|
+
CreateSecurityUpdatePullRequestHandler.Instance,
|
87
|
+
RefreshSecurityUpdatePullRequestHandler.Instance,
|
88
|
+
RefreshVersionUpdatePullRequestHandler.Instance,
|
89
|
+
];
|
90
|
+
|
91
|
+
public static IUpdateHandler GetUpdateHandler(Job job) =>
|
92
|
+
UpdateHandlers.FirstOrDefault(h => h.CanHandle(job)) ?? throw new InvalidOperationException("Unable to find appropriate update handler.");
|
93
|
+
|
94
|
+
private async Task RunScenarioHandlersWithErrorHandlingAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha, ExperimentsManager experimentsManager)
|
95
|
+
{
|
96
|
+
JobErrorBase? error = null;
|
97
|
+
|
98
|
+
try
|
99
|
+
{
|
100
|
+
var handler = GetUpdateHandler(job);
|
101
|
+
_logger.Info($"Starting update job of type {handler.TagName}");
|
102
|
+
await handler.HandleAsync(job, repoContentsPath, baseCommitSha, _discoveryWorker, _analyzeWorker, _updaterWorker, _apiHandler, experimentsManager, _logger);
|
103
|
+
}
|
104
|
+
catch (Exception ex)
|
105
|
+
{
|
106
|
+
error = JobErrorBase.ErrorFromException(ex, _jobId, repoContentsPath.FullName);
|
107
|
+
}
|
108
|
+
|
109
|
+
if (error is not null)
|
110
|
+
{
|
111
|
+
await _apiHandler.RecordUpdateJobError(error);
|
112
|
+
}
|
113
|
+
|
114
|
+
await _apiHandler.MarkAsProcessed(new(baseCommitSha));
|
115
|
+
}
|
116
|
+
|
117
|
+
private async Task<RunResult> RunWithErrorHandlingAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha, ExperimentsManager experimentsManager)
|
61
118
|
{
|
62
119
|
JobErrorBase? error = null;
|
63
120
|
var currentDirectory = repoContentsPath.FullName; // used for error reporting below
|
@@ -71,7 +128,6 @@ public class RunWorker
|
|
71
128
|
{
|
72
129
|
MSBuildHelper.RegisterMSBuild(repoContentsPath.FullName, repoContentsPath.FullName, _logger);
|
73
130
|
|
74
|
-
var experimentsManager = ExperimentsManager.GetExperimentsManager(job.Experiments);
|
75
131
|
var allDependencyFiles = new Dictionary<string, DependencyFile>();
|
76
132
|
foreach (var directory in job.GetAllDirectories())
|
77
133
|
{
|
@@ -109,9 +165,7 @@ public class RunWorker
|
|
109
165
|
private async Task<RunResult> RunForDirectory(Job job, DirectoryInfo repoContentsPath, string repoDirectory, string baseCommitSha, ExperimentsManager experimentsManager)
|
110
166
|
{
|
111
167
|
var discoveryResult = await _discoveryWorker.RunAsync(repoContentsPath.FullName, repoDirectory);
|
112
|
-
|
113
|
-
_logger.Info("Discovery JSON content:");
|
114
|
-
_logger.Info(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
|
168
|
+
_logger.ReportDiscovery(discoveryResult);
|
115
169
|
|
116
170
|
if (discoveryResult.Error is not null)
|
117
171
|
{
|
@@ -125,50 +179,18 @@ public class RunWorker
|
|
125
179
|
}
|
126
180
|
|
127
181
|
// report dependencies
|
128
|
-
var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult
|
182
|
+
var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult);
|
129
183
|
await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
|
130
184
|
|
131
185
|
var incrementMetric = GetIncrementMetric(job);
|
132
186
|
await _apiHandler.IncrementMetric(incrementMetric);
|
133
187
|
|
134
188
|
// TODO: pull out relevant dependencies, then check each for updates and track the changes
|
135
|
-
var originalDependencyFileContents = new Dictionary<string, string>();
|
136
|
-
var originalDependencyFileEOFs = new Dictionary<string, EOLType>();
|
137
|
-
var originalDependencyFileBOMs = new Dictionary<string, bool>();
|
138
189
|
var actualUpdatedDependencies = new List<ReportedDependency>();
|
139
190
|
|
140
191
|
// track original contents for later handling
|
141
|
-
|
142
|
-
|
143
|
-
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
144
|
-
var localFullPath = Path.Join(repoContentsPath.FullName, repoFullPath);
|
145
|
-
var content = await File.ReadAllTextAsync(localFullPath);
|
146
|
-
var rawContent = await File.ReadAllBytesAsync(localFullPath);
|
147
|
-
originalDependencyFileContents[repoFullPath] = content;
|
148
|
-
originalDependencyFileEOFs[repoFullPath] = content.GetPredominantEOL();
|
149
|
-
originalDependencyFileBOMs[repoFullPath] = rawContent.HasBOM();
|
150
|
-
}
|
151
|
-
|
152
|
-
foreach (var project in discoveryResult.Projects)
|
153
|
-
{
|
154
|
-
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
155
|
-
await TrackOriginalContentsAsync(discoveryResult.Path, project.FilePath);
|
156
|
-
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
157
|
-
{
|
158
|
-
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
159
|
-
await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
|
160
|
-
}
|
161
|
-
}
|
162
|
-
|
163
|
-
var nonProjectFiles = new[]
|
164
|
-
{
|
165
|
-
discoveryResult.GlobalJson?.FilePath,
|
166
|
-
discoveryResult.DotNetToolsJson?.FilePath,
|
167
|
-
}.Where(f => f is not null).Cast<string>().ToArray();
|
168
|
-
foreach (var nonProjectFile in nonProjectFiles)
|
169
|
-
{
|
170
|
-
await TrackOriginalContentsAsync(discoveryResult.Path, nonProjectFile);
|
171
|
-
}
|
192
|
+
var tracker = new ModifiedFilesTracker(repoContentsPath);
|
193
|
+
await tracker.StartTrackingAsync(discoveryResult);
|
172
194
|
|
173
195
|
// do update
|
174
196
|
var updateOperationsPerformed = new List<UpdateOperationBase>();
|
@@ -210,8 +232,7 @@ public class RunWorker
|
|
210
232
|
|
211
233
|
var dependencyInfo = GetDependencyInfo(job, dependency);
|
212
234
|
var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
|
213
|
-
_logger.
|
214
|
-
_logger.Info(JsonSerializer.Serialize(analysisResult, AnalyzeWorker.SerializerOptions));
|
235
|
+
_logger.ReportAnalysis(analysisResult);
|
215
236
|
|
216
237
|
if (analysisResult.Error is not null)
|
217
238
|
{
|
@@ -227,7 +248,7 @@ public class RunWorker
|
|
227
248
|
.FirstOrDefault(d => d.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase));
|
228
249
|
if (updatedDependencyFromAnalysis is not null)
|
229
250
|
{
|
230
|
-
var existingPullRequest = job.
|
251
|
+
var existingPullRequest = job.GetExistingPullRequestForDependencies([updatedDependencyFromAnalysis], considerVersions: true);
|
231
252
|
if (existingPullRequest is not null)
|
232
253
|
{
|
233
254
|
await SendApiMessage(new PullRequestExistsForLatestVersion(dependency.Name, analysisResult.UpdatedVersion));
|
@@ -261,6 +282,7 @@ public class RunWorker
|
|
261
282
|
PreviousRequirements = previousDependency.Requirements,
|
262
283
|
};
|
263
284
|
|
285
|
+
var projectDiscovery = discoveryResult.GetProjectDiscoveryFromPath(updateOperation.ProjectPath);
|
264
286
|
var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, updateOperation.ProjectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
|
265
287
|
if (updateResult.Error is not null)
|
266
288
|
{
|
@@ -271,70 +293,19 @@ public class RunWorker
|
|
271
293
|
actualUpdatedDependencies.Add(updatedDependency);
|
272
294
|
}
|
273
295
|
|
274
|
-
|
296
|
+
var patchedUpdateOperations = PatchInOldVersions(updateResult.UpdateOperations, projectDiscovery);
|
297
|
+
updateOperationsPerformed.AddRange(patchedUpdateOperations);
|
275
298
|
}
|
276
299
|
}
|
277
300
|
|
278
301
|
// create PR - we need to manually check file contents; we can't easily use `git status` in tests
|
279
|
-
var updatedDependencyFiles =
|
280
|
-
async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
|
281
|
-
{
|
282
|
-
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
283
|
-
var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
|
284
|
-
var originalContent = originalDependencyFileContents[repoFullPath];
|
285
|
-
var updatedContent = await File.ReadAllTextAsync(localFullPath);
|
286
|
-
|
287
|
-
updatedContent = updatedContent.SetEOL(originalDependencyFileEOFs[repoFullPath]);
|
288
|
-
var updatedRawContent = updatedContent.SetBOM(originalDependencyFileBOMs[repoFullPath]);
|
289
|
-
await File.WriteAllBytesAsync(localFullPath, updatedRawContent);
|
290
|
-
|
291
|
-
if (updatedContent != originalContent)
|
292
|
-
{
|
293
|
-
var reportedContent = updatedContent;
|
294
|
-
var encoding = "utf-8";
|
295
|
-
if (originalDependencyFileBOMs[repoFullPath])
|
296
|
-
{
|
297
|
-
reportedContent = Convert.ToBase64String(updatedRawContent);
|
298
|
-
encoding = "base64";
|
299
|
-
}
|
300
|
-
|
301
|
-
updatedDependencyFiles[localFullPath] = new DependencyFile()
|
302
|
-
{
|
303
|
-
Name = Path.GetFileName(repoFullPath),
|
304
|
-
Directory = Path.GetDirectoryName(repoFullPath)!.NormalizePathToUnix(),
|
305
|
-
Content = reportedContent,
|
306
|
-
ContentEncoding = encoding,
|
307
|
-
};
|
308
|
-
}
|
309
|
-
}
|
310
|
-
|
311
|
-
foreach (var project in discoveryResult.Projects)
|
312
|
-
{
|
313
|
-
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, project.FilePath);
|
314
|
-
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
315
|
-
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
316
|
-
{
|
317
|
-
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
318
|
-
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
|
319
|
-
}
|
320
|
-
}
|
321
|
-
|
322
|
-
foreach (var nonProjectFile in nonProjectFiles)
|
323
|
-
{
|
324
|
-
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, nonProjectFile);
|
325
|
-
}
|
326
|
-
|
327
|
-
var updatedDependencyFileList = updatedDependencyFiles
|
328
|
-
.OrderBy(kvp => kvp.Key)
|
329
|
-
.Select(kvp => kvp.Value)
|
330
|
-
.ToArray();
|
331
|
-
|
302
|
+
var updatedDependencyFiles = await tracker.StopTrackingAsync();
|
332
303
|
var normalizedUpdateOperationsPerformed = UpdateOperationBase.NormalizeUpdateOperationCollection(repoContentsPath.FullName, updateOperationsPerformed);
|
333
304
|
var report = UpdateOperationBase.GenerateUpdateOperationReport(normalizedUpdateOperationsPerformed);
|
334
305
|
_logger.Info(report);
|
335
306
|
|
336
307
|
var sortedUpdatedDependencies = actualUpdatedDependencies.OrderBy(d => d.Name, StringComparer.OrdinalIgnoreCase).ToArray();
|
337
|
-
var resultMessage = GetPullRequestApiMessage(job,
|
308
|
+
var resultMessage = GetPullRequestApiMessage(job, [.. updatedDependencyFiles], sortedUpdatedDependencies, normalizedUpdateOperationsPerformed, baseCommitSha);
|
338
309
|
switch (resultMessage)
|
339
310
|
{
|
340
311
|
case ClosePullRequest close:
|
@@ -366,11 +337,11 @@ public class RunWorker
|
|
366
337
|
|
367
338
|
var result = new RunResult()
|
368
339
|
{
|
369
|
-
Base64DependencyFiles =
|
340
|
+
Base64DependencyFiles = tracker.OriginalDependencyFileContents.OrderBy(kvp => kvp.Key).Select(kvp =>
|
370
341
|
{
|
371
342
|
var fullPath = kvp.Key.FullyNormalizedRootedPath();
|
372
343
|
var rawContent = Encoding.UTF8.GetBytes(kvp.Value);
|
373
|
-
if (
|
344
|
+
if (tracker.OriginalDependencyFileBOMs[kvp.Key])
|
374
345
|
{
|
375
346
|
rawContent = Encoding.UTF8.GetPreamble().Concat(rawContent).ToArray();
|
376
347
|
}
|
@@ -388,6 +359,22 @@ public class RunWorker
|
|
388
359
|
return result;
|
389
360
|
}
|
390
361
|
|
362
|
+
internal static ImmutableArray<UpdateOperationBase> PatchInOldVersions(ImmutableArray<UpdateOperationBase> updateOperations, ProjectDiscoveryResult? projectDiscovery)
|
363
|
+
{
|
364
|
+
if (projectDiscovery is null)
|
365
|
+
{
|
366
|
+
return updateOperations;
|
367
|
+
}
|
368
|
+
|
369
|
+
var originalPackageVersions = projectDiscovery
|
370
|
+
.Dependencies
|
371
|
+
.ToDictionary(d => d.Name, d => d.Version is null ? null : NuGetVersion.Parse(d.Version), StringComparer.OrdinalIgnoreCase);
|
372
|
+
var patchedUpdateOperations = updateOperations
|
373
|
+
.Select(uo => uo with { OldVersion = originalPackageVersions.GetValueOrDefault(uo.DependencyName) })
|
374
|
+
.ToImmutableArray();
|
375
|
+
return patchedUpdateOperations;
|
376
|
+
}
|
377
|
+
|
391
378
|
private async Task SendApiMessage(MessageBase? message)
|
392
379
|
{
|
393
380
|
if (message is null)
|
@@ -433,7 +420,7 @@ public class RunWorker
|
|
433
420
|
if (existingPullRequest is null && updatedFiles.Length == 0)
|
434
421
|
{
|
435
422
|
// it's possible that we were asked to update a specific package, but it's no longer there; in that case find _that_ specific PR
|
436
|
-
var requestedUpdates =
|
423
|
+
var requestedUpdates = job.Dependencies.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
437
424
|
existingPullRequest = existingPullRequests.FirstOrDefault(pr => pr.Item2.Select(d => d.DependencyName).All(requestedUpdates.Contains));
|
438
425
|
}
|
439
426
|
|
@@ -489,6 +476,7 @@ public class RunWorker
|
|
489
476
|
CommitMessage = PullRequestTextGenerator.GetPullRequestCommitMessage(job, updateOperationsPerformed, dependencyGroupName: null),
|
490
477
|
PrTitle = PullRequestTextGenerator.GetPullRequestTitle(job, updateOperationsPerformed, dependencyGroupName: null),
|
491
478
|
PrBody = PullRequestTextGenerator.GetPullRequestBody(job, updateOperationsPerformed, dependencyGroupName: null),
|
479
|
+
DependencyGroup = null,
|
492
480
|
};
|
493
481
|
}
|
494
482
|
}
|
@@ -638,7 +626,7 @@ public class RunWorker
|
|
638
626
|
// not vulnerable => no longer needed
|
639
627
|
var specificJobDependencies = job.SecurityAdvisories
|
640
628
|
.Select(a => a.DependencyName)
|
641
|
-
.Concat(job.Dependencies
|
629
|
+
.Concat(job.Dependencies)
|
642
630
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
643
631
|
if (specificJobDependencies.Contains(dependency.Name))
|
644
632
|
{
|
@@ -652,7 +640,7 @@ public class RunWorker
|
|
652
640
|
{
|
653
641
|
// not a security update, so only update if...
|
654
642
|
// ...we've been explicitly asked to update this
|
655
|
-
if (
|
643
|
+
if (job.Dependencies.Any(d => d.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)))
|
656
644
|
{
|
657
645
|
return true;
|
658
646
|
}
|
@@ -708,7 +696,7 @@ public class RunWorker
|
|
708
696
|
return dependencyInfo;
|
709
697
|
}
|
710
698
|
|
711
|
-
internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult
|
699
|
+
internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult)
|
712
700
|
{
|
713
701
|
string GetFullRepoPath(string path)
|
714
702
|
{
|