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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs +40 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +0 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +144 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/IGitCommandHandler.cs +6 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/ShellGitCommandHandler.cs +37 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +5 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +1 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +9 -2
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs +13 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +2 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +6 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +5 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +5 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdateNotPossible.cs +5 -1
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +8 -2
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +73 -31
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +30 -9
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +3 -3
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +2 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +183 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/TestGitCommandHandler.cs +16 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +640 -17
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +10 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +53 -6
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs +225 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +106 -0
  29. 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
- public static async ValueTask UpdateBindingRedirectsAsync(ProjectBuildFile projectBuildFile)
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
- var fileContent = AddBindingRedirects(configFile, bindings);
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> bindingRedirects)
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 (!bindingRedirects.Any())
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 bindingRedirects)
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
- // Get an assembly binding element to use
212
- var assemblyBindingElement = GetAssemblyBindingElement(runtime);
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
- // Add the binding to that element
215
- assemblyBindingElement.AddIndented(bindingRedirect.ToXElement());
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
+ }