dependabot-nuget 0.283.0 → 0.285.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/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
|
+
}
|