dependabot-nuget 0.283.0 → 0.285.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +40 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +0 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +144 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/IGitCommandHandler.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/ShellGitCommandHandler.cs +37 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +5 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +9 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +6 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +5 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +5 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +5 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +8 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +73 -31
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +30 -9
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +3 -3
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +2 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +183 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/TestGitCommandHandler.cs +16 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +640 -17
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +10 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +53 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs +225 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +106 -0
- metadata +13 -5
@@ -28,7 +28,9 @@ internal static class BindingRedirectManager
|
|
28
28
|
/// https://learn.microsoft.com/en-us/nuget/resources/check-project-format
|
29
29
|
/// </remarks>
|
30
30
|
/// <param name="projectBuildFile">The project build file (*.xproj) to be updated</param>
|
31
|
-
|
31
|
+
/// <param name="updatedPackageName"/>The name of the package that was updated</param>
|
32
|
+
/// <param name="updatedPackageVersion">The version of the package that was updated</param>
|
33
|
+
public static async ValueTask UpdateBindingRedirectsAsync(ProjectBuildFile projectBuildFile, string updatedPackageName, string updatedPackageVersion)
|
32
34
|
{
|
33
35
|
var configFile = await TryGetRuntimeConfigurationFile(projectBuildFile);
|
34
36
|
if (configFile is null)
|
@@ -47,7 +49,20 @@ internal static class BindingRedirectManager
|
|
47
49
|
return;
|
48
50
|
}
|
49
51
|
|
50
|
-
|
52
|
+
// we need to detect what assembly references come from the newly updated package; the `HintPath` will look like
|
53
|
+
// ..\packages\Some.Package.1.2.3\lib\net45\Some.Package.dll
|
54
|
+
// so we first pull out the packages sub-path, e.g., `..\packages`
|
55
|
+
// then we add the updated package name, version, and a trailing directory separator and ensure it's a unix-style path
|
56
|
+
// e.g., ../packages/Some.Package/1.2.3/
|
57
|
+
// at this point any assembly in that directory is from the updated package and will need a binding redirect
|
58
|
+
// finally we pull out the assembly `HintPath` values for _all_ references relative to the project file in a unix-style value
|
59
|
+
// e.g., ../packages/Some.Other.Package/4.5.6/lib/net45/Some.Other.Package.dll
|
60
|
+
// all of that is passed to `AddBindingRedirects()` so we can ensure binding redirects for the relevant assemblies
|
61
|
+
var packagesDirectory = PackagesConfigUpdater.GetPathToPackagesDirectory(projectBuildFile, updatedPackageName, updatedPackageVersion, packagesConfigPath: null)!;
|
62
|
+
var assemblyPathPrefix = Path.Combine(packagesDirectory, $"{updatedPackageName}.{updatedPackageVersion}").NormalizePathToUnix().EnsureSuffix("/");
|
63
|
+
var assemblyPaths = references.Select(static x => x.HintPath).Select(x => Path.GetRelativePath(Path.GetDirectoryName(projectBuildFile.Path)!, x).NormalizePathToUnix()).ToList();
|
64
|
+
var bindingsAndAssemblyPaths = bindings.Zip(assemblyPaths);
|
65
|
+
var fileContent = AddBindingRedirects(configFile, bindingsAndAssemblyPaths, assemblyPathPrefix);
|
51
66
|
configFile = configFile with { Content = fileContent };
|
52
67
|
|
53
68
|
await File.WriteAllTextAsync(configFile.Path, configFile.Content);
|
@@ -161,10 +176,10 @@ internal static class BindingRedirectManager
|
|
161
176
|
}
|
162
177
|
}
|
163
178
|
|
164
|
-
private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable<Runtime_AssemblyBinding>
|
179
|
+
private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable<(Runtime_AssemblyBinding Binding, string AssemblyPath)> bindingRedirectsAndAssemblyPaths, string assemblyPathPrefix)
|
165
180
|
{
|
166
181
|
// Do nothing if there are no binding redirects to add, bail out
|
167
|
-
if (!
|
182
|
+
if (!bindingRedirectsAndAssemblyPaths.Any())
|
168
183
|
{
|
169
184
|
return configFile.Content;
|
170
185
|
}
|
@@ -185,7 +200,7 @@ internal static class BindingRedirectManager
|
|
185
200
|
// Get all of the current bindings in config
|
186
201
|
var currentBindings = GetAssemblyBindings(runtime);
|
187
202
|
|
188
|
-
foreach (var bindingRedirect in
|
203
|
+
foreach (var (bindingRedirect, assemblyPath) in bindingRedirectsAndAssemblyPaths)
|
189
204
|
{
|
190
205
|
// If the binding redirect already exists in config, update it. Otherwise, add it.
|
191
206
|
var bindingAssemblyIdentity = new AssemblyIdentity(bindingRedirect.Name, bindingRedirect.PublicKeyToken);
|
@@ -208,11 +223,17 @@ internal static class BindingRedirectManager
|
|
208
223
|
}
|
209
224
|
else
|
210
225
|
{
|
211
|
-
//
|
212
|
-
|
226
|
+
// only add a previously missing binding redirect if it's related to the package that caused the whole update
|
227
|
+
// this isn't strictly necessary, but can be helpful to the end user and it's easy for them to revert if they
|
228
|
+
// don't like this particular change
|
229
|
+
if (assemblyPath.StartsWith(assemblyPathPrefix, StringComparison.OrdinalIgnoreCase))
|
230
|
+
{
|
231
|
+
// Get an assembly binding element to use
|
232
|
+
var assemblyBindingElement = GetAssemblyBindingElement(runtime);
|
213
233
|
|
214
|
-
|
215
|
-
|
234
|
+
// Add the binding to that element
|
235
|
+
assemblyBindingElement.AddIndented(bindingRedirect.ToXElement());
|
236
|
+
}
|
216
237
|
}
|
217
238
|
}
|
218
239
|
|
@@ -93,7 +93,7 @@ internal static class PackagesConfigUpdater
|
|
93
93
|
projectBuildFile.NormalizeDirectorySeparatorsInProject();
|
94
94
|
|
95
95
|
// Update binding redirects
|
96
|
-
await BindingRedirectManager.UpdateBindingRedirectsAsync(projectBuildFile);
|
96
|
+
await BindingRedirectManager.UpdateBindingRedirectsAsync(projectBuildFile, dependencyName, newDependencyVersion);
|
97
97
|
|
98
98
|
logger.Log(" Writing project file back to disk");
|
99
99
|
await projectBuildFile.SaveAsync();
|
@@ -196,7 +196,7 @@ internal static class PackagesConfigUpdater
|
|
196
196
|
return processes;
|
197
197
|
}
|
198
198
|
|
199
|
-
internal static string? GetPathToPackagesDirectory(ProjectBuildFile projectBuildFile, string dependencyName, string dependencyVersion, string packagesConfigPath)
|
199
|
+
internal static string? GetPathToPackagesDirectory(ProjectBuildFile projectBuildFile, string dependencyName, string dependencyVersion, string? packagesConfigPath)
|
200
200
|
{
|
201
201
|
// the packages directory can be found from the hint path of the matching dependency, e.g., when given "Newtonsoft.Json", "7.0.1", and a project like this:
|
202
202
|
// <Project>
|
@@ -242,7 +242,7 @@ internal static class PackagesConfigUpdater
|
|
242
242
|
}
|
243
243
|
}
|
244
244
|
|
245
|
-
if (partialPathMatch is null)
|
245
|
+
if (partialPathMatch is null && packagesConfigPath is not null)
|
246
246
|
{
|
247
247
|
// if we got this far, we couldn't find the packages directory for the specified dependency and there are 2 possibilities:
|
248
248
|
// 1. the dependency doesn't actually exist in this project
|
@@ -27,6 +27,8 @@ internal static class PathHelper
|
|
27
27
|
|
28
28
|
public static string EnsurePrefix(this string s, string prefix) => s.StartsWith(prefix) ? s : prefix + s;
|
29
29
|
|
30
|
+
public static string EnsureSuffix(this string s, string suffix) => s.EndsWith(suffix) ? s : s + suffix;
|
31
|
+
|
30
32
|
public static string NormalizePathToUnix(this string path) => path.Replace("\\", "/");
|
31
33
|
|
32
34
|
public static string NormalizeUnixPathParts(this string path)
|
@@ -0,0 +1,183 @@
|
|
1
|
+
using NuGetUpdater.Core.Clone;
|
2
|
+
using NuGetUpdater.Core.Run.ApiModel;
|
3
|
+
using NuGetUpdater.Core.Test.Run;
|
4
|
+
|
5
|
+
using Xunit;
|
6
|
+
|
7
|
+
namespace NuGetUpdater.Core.Test.Clone;
|
8
|
+
|
9
|
+
public class CloneWorkerTests
|
10
|
+
{
|
11
|
+
private const string TestRepoPath = "TEST/REPO/PATH";
|
12
|
+
|
13
|
+
[Fact]
|
14
|
+
public void CloneCommandsAreGenerated()
|
15
|
+
{
|
16
|
+
TestCommands(
|
17
|
+
provider: "github",
|
18
|
+
repoMoniker: "test/repo",
|
19
|
+
expectedCommands:
|
20
|
+
[
|
21
|
+
(["clone", "--no-tags", "--depth", "1", "--recurse-submodules", "--shallow-submodules", "https://github.com/test/repo", TestRepoPath], null)
|
22
|
+
]
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
[Fact]
|
27
|
+
public void CloneCommandsAreGeneratedWhenBranchIsSpecified()
|
28
|
+
{
|
29
|
+
TestCommands(
|
30
|
+
provider: "github",
|
31
|
+
repoMoniker: "test/repo",
|
32
|
+
branch: "some-branch",
|
33
|
+
expectedCommands:
|
34
|
+
[
|
35
|
+
(["clone", "--no-tags", "--depth", "1", "--recurse-submodules", "--shallow-submodules", "--branch", "some-branch", "--single-branch", "https://github.com/test/repo", TestRepoPath], null)
|
36
|
+
]
|
37
|
+
);
|
38
|
+
}
|
39
|
+
|
40
|
+
[Fact]
|
41
|
+
public void CloneCommandsAreGeneratedWhenCommitIsSpecified()
|
42
|
+
{
|
43
|
+
TestCommands(
|
44
|
+
provider: "github",
|
45
|
+
repoMoniker: "test/repo",
|
46
|
+
commit: "abc123",
|
47
|
+
expectedCommands:
|
48
|
+
[
|
49
|
+
(["clone", "--no-tags", "--depth", "1", "--recurse-submodules", "--shallow-submodules", "https://github.com/test/repo", TestRepoPath], null),
|
50
|
+
(["fetch", "--depth", "1", "--recurse-submodules=on-demand", "origin", "abc123"], TestRepoPath),
|
51
|
+
(["reset", "--hard", "--recurse-submodules", "abc123"], TestRepoPath)
|
52
|
+
]
|
53
|
+
);
|
54
|
+
}
|
55
|
+
|
56
|
+
[Fact]
|
57
|
+
public async Task SuccessfulCloneGeneratesNoApiMessages()
|
58
|
+
{
|
59
|
+
await TestCloneAsync(
|
60
|
+
provider: "github",
|
61
|
+
repoMoniker: "test/repo",
|
62
|
+
expectedApiMessages: []
|
63
|
+
);
|
64
|
+
}
|
65
|
+
|
66
|
+
[Fact]
|
67
|
+
public async Task UnauthorizedCloneGeneratesTheExpectedApiMessagesFromGenericOutput()
|
68
|
+
{
|
69
|
+
await TestCloneAsync(
|
70
|
+
provider: "github",
|
71
|
+
repoMoniker: "test/repo",
|
72
|
+
testGitCommandHandler: new TestGitCommandHandlerWithOutputs("Authentication failed for repo", ""),
|
73
|
+
expectedApiMessages:
|
74
|
+
[
|
75
|
+
new JobRepoNotFound("Authentication failed for repo"),
|
76
|
+
new MarkAsProcessed("unknown"),
|
77
|
+
],
|
78
|
+
expectedExitCode: 1
|
79
|
+
);
|
80
|
+
}
|
81
|
+
|
82
|
+
[Fact]
|
83
|
+
public async Task UnauthorizedCloneGeneratesTheExpectedApiMessagesFromGitCommandOutput()
|
84
|
+
{
|
85
|
+
await TestCloneAsync(
|
86
|
+
provider: "github",
|
87
|
+
repoMoniker: "test/repo",
|
88
|
+
testGitCommandHandler: new TestGitCommandHandlerWithOutputs("", "fatal: could not read Username for 'https://github.com': No such device or address"),
|
89
|
+
expectedApiMessages:
|
90
|
+
[
|
91
|
+
new JobRepoNotFound("fatal: could not read Username for 'https://github.com': No such device or address"),
|
92
|
+
new MarkAsProcessed("unknown"),
|
93
|
+
],
|
94
|
+
expectedExitCode: 1
|
95
|
+
);
|
96
|
+
}
|
97
|
+
|
98
|
+
private class TestGitCommandHandlerWithOutputs : TestGitCommandHandler
|
99
|
+
{
|
100
|
+
private readonly string _stdout;
|
101
|
+
private readonly string _stderr;
|
102
|
+
|
103
|
+
public TestGitCommandHandlerWithOutputs(string stdout, string stderr)
|
104
|
+
{
|
105
|
+
_stdout = stdout;
|
106
|
+
_stderr = stderr;
|
107
|
+
}
|
108
|
+
|
109
|
+
public override async Task RunGitCommandAsync(IReadOnlyCollection<string> args, string? workingDirectory = null)
|
110
|
+
{
|
111
|
+
await base.RunGitCommandAsync(args, workingDirectory);
|
112
|
+
ShellGitCommandHandler.HandleErrorsFromOutput(_stdout, _stderr);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
private static void TestCommands(string provider, string repoMoniker, (string[] Args, string? WorkingDirectory)[] expectedCommands, string? branch = null, string? commit = null)
|
117
|
+
{
|
118
|
+
var job = new Job()
|
119
|
+
{
|
120
|
+
Source = new()
|
121
|
+
{
|
122
|
+
Provider = provider,
|
123
|
+
Repo = repoMoniker,
|
124
|
+
Branch = branch,
|
125
|
+
Commit = commit,
|
126
|
+
}
|
127
|
+
};
|
128
|
+
var actualCommands = CloneWorker.GetAllCommandArgs(job, TestRepoPath);
|
129
|
+
VerifyCommands(expectedCommands, actualCommands);
|
130
|
+
}
|
131
|
+
|
132
|
+
private static async Task TestCloneAsync(string provider, string repoMoniker, object[] expectedApiMessages, string? branch = null, string? commit = null, TestGitCommandHandler? testGitCommandHandler = null, int expectedExitCode = 0)
|
133
|
+
{
|
134
|
+
// arrange
|
135
|
+
var testApiHandler = new TestApiHandler();
|
136
|
+
testGitCommandHandler ??= new TestGitCommandHandler();
|
137
|
+
var testLogger = new TestLogger();
|
138
|
+
var worker = new CloneWorker(testApiHandler, testGitCommandHandler, testLogger);
|
139
|
+
|
140
|
+
// act
|
141
|
+
var job = new Job()
|
142
|
+
{
|
143
|
+
Source = new()
|
144
|
+
{
|
145
|
+
Provider = provider,
|
146
|
+
Repo = repoMoniker,
|
147
|
+
Branch = branch,
|
148
|
+
Commit = commit,
|
149
|
+
}
|
150
|
+
};
|
151
|
+
var exitCode = await worker.RunAsync(job, TestRepoPath);
|
152
|
+
|
153
|
+
// assert
|
154
|
+
Assert.Equal(expectedExitCode, exitCode);
|
155
|
+
|
156
|
+
var actualApiMessages = testApiHandler.ReceivedMessages.ToArray();
|
157
|
+
if (actualApiMessages.Length > expectedApiMessages.Length)
|
158
|
+
{
|
159
|
+
var extraApiMessages = actualApiMessages.Skip(expectedApiMessages.Length).Select(m => RunWorkerTests.SerializeObjectAndType(m.Object)).ToArray();
|
160
|
+
Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but got {extraApiMessages.Length} extra:\n\t{string.Join("\n\t", extraApiMessages)}");
|
161
|
+
}
|
162
|
+
if (expectedApiMessages.Length > actualApiMessages.Length)
|
163
|
+
{
|
164
|
+
var missingApiMessages = expectedApiMessages.Skip(actualApiMessages.Length).Select(m => RunWorkerTests.SerializeObjectAndType(m)).ToArray();
|
165
|
+
Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but only got {actualApiMessages.Length}; missing:\n\t{string.Join("\n\t", missingApiMessages)}");
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
private static void VerifyCommands((string[] Args, string? WorkingDirectory)[] expected, (string[] Args, string? WorkingDirectory)[] actual)
|
170
|
+
{
|
171
|
+
var expectedCommands = StringifyCommands(expected);
|
172
|
+
var actualCommands = StringifyCommands(actual);
|
173
|
+
Assert.True(expectedCommands.Length == actualCommands.Length, $"Expected {expectedCommands.Length} messages:\n\t{string.Join("\n\t", expectedCommands)}\ngot {actualCommands.Length}:\n\t{string.Join("\n\t", actualCommands)}");
|
174
|
+
foreach (var (expectedCommand, actualCommand) in expectedCommands.Zip(actualCommands))
|
175
|
+
{
|
176
|
+
Assert.Equal(expectedCommand, actualCommand);
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
private static string[] StringifyCommands((string[] Args, string? WorkingDirectory)[] commandArgs) => commandArgs.Select(a => StringifyCommand(a.Args, a.WorkingDirectory)).ToArray();
|
181
|
+
private static string StringifyCommand(string[] args, string? workingDirectory) => $"args=[{string.Join(", ", args)}], workingDirectory={ReplaceWorkingDirectory(workingDirectory)}";
|
182
|
+
private static string ReplaceWorkingDirectory(string? arg) => arg ?? "NULL";
|
183
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
using NuGetUpdater.Core.Clone;
|
2
|
+
|
3
|
+
namespace NuGetUpdater.Core.Test.Clone;
|
4
|
+
|
5
|
+
internal class TestGitCommandHandler : IGitCommandHandler
|
6
|
+
{
|
7
|
+
private readonly List<(string[] Args, string? WorkingDirectory)> _seenCommands = new();
|
8
|
+
|
9
|
+
public IReadOnlyCollection<(string[] Args, string? WorkingDirectory)> SeenCommands => _seenCommands;
|
10
|
+
|
11
|
+
public virtual Task RunGitCommandAsync(IReadOnlyCollection<string> args, string? workingDirectory = null)
|
12
|
+
{
|
13
|
+
_seenCommands.Add((args.ToArray(), workingDirectory));
|
14
|
+
return Task.CompletedTask;
|
15
|
+
}
|
16
|
+
}
|