dependabot-nuget 0.282.0 → 0.284.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) 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 +5 -17
  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 +4 -15
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +10 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs +225 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +106 -0
  28. metadata +13 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40a4e33161303780b957de20221c3daf909d922bdff17bd22dead842f80a585e
4
- data.tar.gz: f3faa68e7a0d0ef1e3a431acb17569b88309802cffb7be3315d080d0a2ec5f33
3
+ metadata.gz: 0604d43cad05ef88f7471259b903ec56c1cf7d1bcf127b253bf6c0c2af8c66f0
4
+ data.tar.gz: 9e4b41b0d72f5989abb57bc18f39116a1a2f5c00f935c7a0c2858e61da6bcb75
5
5
  SHA512:
6
- metadata.gz: 2d6e0c85681853fcd2d8a539ac2c4b116b8837442073954cd0fc4b717a45ce17cffa8d16f9f3e5d6e9db1f0beffdedb9577e56a05652285e706efabeedef1338
7
- data.tar.gz: 1ba69c4b218959952b6d86553b24871bbbf43d0a443f35f9de886bdfa78029ae7ea9885c9b8247cb98e5ab39e6e8ea8c923ce4cec60404251791482da7ee923c
6
+ metadata.gz: 6f4b7300174349f5a5c5a5ebdc461a18ea2c08a06b0229043216e2f3b19bc5a5c2523f43735f0aeafd95f0bffea693791f307165dbb22a1d58ab81f19e89496e
7
+ data.tar.gz: '0761288866aa1af50029cbd2f61d2423e06f51d090ed8ca4afb3b5afb9a4aa4b5389fa57a1fdad03b0de95895d3b43eb612cbb992660b44ca854bcc8303f1733'
@@ -0,0 +1,40 @@
1
+ using System.CommandLine;
2
+
3
+ using NuGetUpdater.Core;
4
+ using NuGetUpdater.Core.Clone;
5
+ using NuGetUpdater.Core.Run;
6
+
7
+ namespace NuGetUpdater.Cli.Commands;
8
+
9
+ internal static class CloneCommand
10
+ {
11
+ internal static readonly Option<FileInfo> JobPathOption = new("--job-path") { IsRequired = true };
12
+ internal static readonly Option<DirectoryInfo> RepoContentsPathOption = new("--repo-contents-path") { IsRequired = true };
13
+ internal static readonly Option<Uri> ApiUrlOption = new("--api-url") { IsRequired = true };
14
+ internal static readonly Option<string> JobIdOption = new("--job-id") { IsRequired = true };
15
+
16
+ internal static Command GetCommand(Action<int> setExitCode)
17
+ {
18
+ var command = new Command("clone", "Clones a repository in preparation for a dependabot job.")
19
+ {
20
+ JobPathOption,
21
+ RepoContentsPathOption,
22
+ ApiUrlOption,
23
+ JobIdOption,
24
+ };
25
+
26
+ command.TreatUnmatchedTokensAsErrors = true;
27
+
28
+ command.SetHandler(async (jobPath, repoContentsPath, apiUrl, jobId) =>
29
+ {
30
+ var apiHandler = new HttpApiHandler(apiUrl.ToString(), jobId);
31
+ var logger = new ConsoleLogger();
32
+ var gitCommandHandler = new ShellGitCommandHandler(logger);
33
+ var worker = new CloneWorker(apiHandler, gitCommandHandler, logger);
34
+ var exitCode = await worker.RunAsync(jobPath, repoContentsPath);
35
+ setExitCode(exitCode);
36
+ }, JobPathOption, RepoContentsPathOption, ApiUrlOption, JobIdOption);
37
+
38
+ return command;
39
+ }
40
+ }
@@ -13,6 +13,7 @@ internal sealed class Program
13
13
 
14
14
  var command = new RootCommand
15
15
  {
16
+ CloneCommand.GetCommand(setExitCode),
16
17
  FrameworkCheckCommand.GetCommand(setExitCode),
17
18
  DiscoverCommand.GetCommand(setExitCode),
18
19
  AnalyzeCommand.GetCommand(setExitCode),
@@ -41,7 +41,6 @@ public partial class EntryPointTests
41
41
  ],
42
42
  job: new Job()
43
43
  {
44
- PackageManager = "nuget",
45
44
  AllowedUpdates = [
46
45
  new()
47
46
  {
@@ -0,0 +1,144 @@
1
+ using System.Net;
2
+
3
+ using NuGetUpdater.Core.Run;
4
+ using NuGetUpdater.Core.Run.ApiModel;
5
+
6
+ using CommandArguments = (string[] Args, string? WorkingDirectory);
7
+
8
+ namespace NuGetUpdater.Core.Clone;
9
+
10
+ public class CloneWorker
11
+ {
12
+ private readonly IApiHandler _apiHandler;
13
+ private readonly IGitCommandHandler _gitCommandHandler;
14
+ private readonly ILogger _logger;
15
+
16
+ public CloneWorker(IApiHandler apiHandler, IGitCommandHandler gitCommandHandler, ILogger logger)
17
+ {
18
+ _apiHandler = apiHandler;
19
+ _gitCommandHandler = gitCommandHandler;
20
+ _logger = logger;
21
+ }
22
+
23
+ // entrypoint for cli
24
+ public async Task<int> RunAsync(FileInfo jobFilePath, DirectoryInfo repoContentsPath)
25
+ {
26
+ var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
27
+ var jobWrapper = RunWorker.Deserialize(jobFileContent);
28
+ var result = await RunAsync(jobWrapper.Job, repoContentsPath.FullName);
29
+ return result;
30
+ }
31
+
32
+ // object model entry point
33
+ public async Task<int> RunAsync(Job job, string repoContentsPath)
34
+ {
35
+ JobErrorBase? error = null;
36
+ try
37
+ {
38
+ var commandArgs = GetAllCommandArgs(job, repoContentsPath);
39
+ foreach (var (args, workingDirectory) in commandArgs)
40
+ {
41
+ await _gitCommandHandler.RunGitCommandAsync(args, workingDirectory);
42
+ }
43
+ }
44
+ catch (HttpRequestException ex)
45
+ when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
46
+ {
47
+ error = new JobRepoNotFound(ex.Message);
48
+ }
49
+ catch (Exception ex)
50
+ {
51
+ error = new UnknownError(ex.ToString());
52
+ }
53
+
54
+ if (error is not null)
55
+ {
56
+ await _apiHandler.RecordUpdateJobError(error);
57
+ await _apiHandler.MarkAsProcessed(new("unknown"));
58
+ return 1;
59
+ }
60
+
61
+ return 0;
62
+ }
63
+
64
+ internal static CommandArguments[] GetAllCommandArgs(Job job, string repoContentsPath)
65
+ {
66
+ var commandArgs = new List<CommandArguments>()
67
+ {
68
+ GetCloneArgs(job, repoContentsPath)
69
+ };
70
+
71
+ if (job.Source.Commit is { } commit)
72
+ {
73
+ commandArgs.Add(GetFetchArgs(commit, repoContentsPath));
74
+ commandArgs.Add(GetResetArgs(commit, repoContentsPath));
75
+ }
76
+
77
+ return commandArgs.ToArray();
78
+ }
79
+
80
+ internal static CommandArguments GetCloneArgs(Job job, string repoContentsPath)
81
+ {
82
+ var url = GetRepoUrl(job);
83
+ var args = new List<string>()
84
+ {
85
+ "clone",
86
+ "--no-tags",
87
+ "--depth",
88
+ "1",
89
+ "--recurse-submodules",
90
+ "--shallow-submodules",
91
+ };
92
+
93
+ if (job.Source.Branch is { } branch)
94
+ {
95
+ args.Add("--branch");
96
+ args.Add(branch);
97
+ args.Add("--single-branch");
98
+ }
99
+
100
+ args.Add(url);
101
+ args.Add(repoContentsPath);
102
+ return (args.ToArray(), null);
103
+ }
104
+
105
+ internal static CommandArguments GetFetchArgs(string commit, string repoContentsPath)
106
+ {
107
+ return
108
+ (
109
+ [
110
+ "fetch",
111
+ "--depth",
112
+ "1",
113
+ "--recurse-submodules=on-demand",
114
+ "origin",
115
+ commit
116
+ ],
117
+ repoContentsPath
118
+ );
119
+ }
120
+
121
+ internal static CommandArguments GetResetArgs(string commit, string repoContentsPath)
122
+ {
123
+ return
124
+ (
125
+ [
126
+ "reset",
127
+ "--hard",
128
+ "--recurse-submodules",
129
+ commit
130
+ ],
131
+ repoContentsPath
132
+ );
133
+ }
134
+
135
+ private static string GetRepoUrl(Job job)
136
+ {
137
+ return job.Source.Provider switch
138
+ {
139
+ "azure" => $"https://dev.azure.com/{job.Source.Repo}",
140
+ "github" => $"https://github.com/{job.Source.Repo}",
141
+ _ => throw new ArgumentException($"Unknown provider: {job.Source.Provider}")
142
+ };
143
+ }
144
+ }
@@ -0,0 +1,6 @@
1
+ namespace NuGetUpdater.Core.Clone;
2
+
3
+ public interface IGitCommandHandler
4
+ {
5
+ Task RunGitCommandAsync(IReadOnlyCollection<string> args, string? workingDirectory = null);
6
+ }
@@ -0,0 +1,37 @@
1
+ using System.Net;
2
+
3
+ namespace NuGetUpdater.Core.Clone;
4
+
5
+ public class ShellGitCommandHandler : IGitCommandHandler
6
+ {
7
+ private readonly ILogger _logger;
8
+
9
+ public ShellGitCommandHandler(ILogger logger)
10
+ {
11
+ _logger = logger;
12
+ }
13
+
14
+ public async Task RunGitCommandAsync(IReadOnlyCollection<string> args, string? workingDirectory = null)
15
+ {
16
+ _logger.Log($"Running command: git {string.Join(" ", args)}{(workingDirectory is null ? "" : $" in directory {workingDirectory}")}");
17
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("git", args, workingDirectory);
18
+ HandleErrorsFromOutput(stdout, stderr);
19
+ }
20
+
21
+ internal static void HandleErrorsFromOutput(string stdout, string stderr)
22
+ {
23
+ foreach (var output in new[] { stdout, stderr })
24
+ {
25
+ ThrowOnUnauthenticated(output);
26
+ }
27
+ }
28
+
29
+ private static void ThrowOnUnauthenticated(string output)
30
+ {
31
+ if (output.Contains("Authentication failed for") ||
32
+ output.Contains("could not read Username for"))
33
+ {
34
+ throw new HttpRequestException(output, inner: null, statusCode: HttpStatusCode.Unauthorized);
35
+ }
36
+ }
37
+ }
@@ -2,5 +2,9 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public record DependencyFileNotFound : JobErrorBase
4
4
  {
5
- public override string Type => "dependency_file_not_found";
5
+ public DependencyFileNotFound(string filePath)
6
+ : base("dependency_file_not_found")
7
+ {
8
+ Details = filePath;
9
+ }
6
10
  }
@@ -2,7 +2,7 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public sealed record Job
4
4
  {
5
- public required string PackageManager { get; init; }
5
+ public string PackageManager { get; init; } = "nuget";
6
6
  public AllowedUpdate[]? AllowedUpdates { get; init; } = null;
7
7
  public bool Debug { get; init; } = false;
8
8
  public object[]? DependencyGroups { get; init; } = null;
@@ -4,8 +4,15 @@ namespace NuGetUpdater.Core.Run.ApiModel;
4
4
 
5
5
  public abstract record JobErrorBase
6
6
  {
7
+ public JobErrorBase(string type)
8
+ {
9
+ Type = type;
10
+ }
11
+
7
12
  [JsonPropertyName("error-type")]
8
- public abstract string Type { get; }
13
+ public string Type { get; }
14
+
9
15
  [JsonPropertyName("error-details")]
10
- public required object Details { get; init; }
16
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
17
+ public object? Details { get; init; } = null;
11
18
  }
@@ -0,0 +1,13 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public record JobRepoNotFound : JobErrorBase
4
+ {
5
+ public JobRepoNotFound(string message)
6
+ : base("job_repo_not_found")
7
+ {
8
+ Details = new Dictionary<string, string>()
9
+ {
10
+ ["message"] = message
11
+ };
12
+ }
13
+ }
@@ -4,6 +4,8 @@ public sealed class JobSource
4
4
  {
5
5
  public required string Provider { get; init; }
6
6
  public required string Repo { get; init; }
7
+ public string? Branch { get; init; } = null;
8
+ public string? Commit { get; init; } = null;
7
9
  public string? Directory { get; init; } = null;
8
10
  public string[]? Directories { get; init; } = null;
9
11
  public string? Hostname { get; init; } = null;
@@ -4,6 +4,11 @@ namespace NuGetUpdater.Core.Run.ApiModel;
4
4
 
5
5
  public sealed record MarkAsProcessed
6
6
  {
7
+ public MarkAsProcessed(string baseCommitSha)
8
+ {
9
+ BaseCommitSha = baseCommitSha;
10
+ }
11
+
7
12
  [JsonPropertyName("base-commit-sha")]
8
- public required string BaseCommitSha { get; init; }
13
+ public string BaseCommitSha { get; }
9
14
  }
@@ -2,5 +2,9 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public record PrivateSourceAuthenticationFailure : JobErrorBase
4
4
  {
5
- public override string Type => "private_source_authentication_failure";
5
+ public PrivateSourceAuthenticationFailure(string[] urls)
6
+ : base("private_source_authentication_failure")
7
+ {
8
+ Details = $"({string.Join("|", urls)})";
9
+ }
6
10
  }
@@ -2,5 +2,9 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public record UnknownError : JobErrorBase
4
4
  {
5
- public override string Type => "unknown_error";
5
+ public UnknownError(string details)
6
+ : base("unknown_error")
7
+ {
8
+ Details = details;
9
+ }
6
10
  }
@@ -2,5 +2,9 @@ namespace NuGetUpdater.Core.Run.ApiModel;
2
2
 
3
3
  public record UpdateNotPossible : JobErrorBase
4
4
  {
5
- public override string Type => "update_not_possible";
5
+ public UpdateNotPossible(string[] dependencies)
6
+ : base("update_not_possible")
7
+ {
8
+ Details = dependencies;
9
+ }
6
10
  }
@@ -50,13 +50,19 @@ public class HttpApiHandler : IApiHandler
50
50
  await PostAsJson("mark_as_processed", markAsProcessed);
51
51
  }
52
52
 
53
- private async Task PostAsJson(string endpoint, object body)
53
+ internal static string Serialize(object body)
54
54
  {
55
55
  var wrappedBody = new
56
56
  {
57
- Data = body,
57
+ Data = body
58
58
  };
59
59
  var payload = JsonSerializer.Serialize(wrappedBody, SerializerOptions);
60
+ return payload;
61
+ }
62
+
63
+ private async Task PostAsJson(string endpoint, object body)
64
+ {
65
+ var payload = Serialize(body);
60
66
  var content = new StringContent(payload, Encoding.UTF8, "application/json");
61
67
  var response = await HttpClient.PostAsync($"{_apiUrl}/update_jobs/{_jobId}/{endpoint}", content);
62
68
  var _ = response.EnsureSuccessStatusCode();
@@ -76,31 +76,19 @@ public class RunWorker
76
76
  catch (HttpRequestException ex)
77
77
  when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
78
78
  {
79
- error = new PrivateSourceAuthenticationFailure()
80
- {
81
- Details = $"({string.Join("|", lastUsedPackageSourceUrls)})",
82
- };
79
+ error = new PrivateSourceAuthenticationFailure(lastUsedPackageSourceUrls);
83
80
  }
84
81
  catch (MissingFileException ex)
85
82
  {
86
- error = new DependencyFileNotFound()
87
- {
88
- Details = ex.FilePath,
89
- };
83
+ error = new DependencyFileNotFound(ex.FilePath);
90
84
  }
91
85
  catch (UpdateNotPossibleException ex)
92
86
  {
93
- error = new UpdateNotPossible()
94
- {
95
- Details = ex.Dependencies,
96
- };
87
+ error = new UpdateNotPossible(ex.Dependencies);
97
88
  }
98
89
  catch (Exception ex)
99
90
  {
100
- error = new UnknownError()
101
- {
102
- Details = ex.ToString(),
103
- };
91
+ error = new UnknownError(ex.ToString());
104
92
  }
105
93
 
106
94
  if (error is not null)
@@ -108,7 +96,7 @@ public class RunWorker
108
96
  await _apiHandler.RecordUpdateJobError(error);
109
97
  }
110
98
 
111
- await _apiHandler.MarkAsProcessed(new() { BaseCommitSha = baseCommitSha });
99
+ await _apiHandler.MarkAsProcessed(new(baseCommitSha));
112
100
 
113
101
  return runResult;
114
102
  }
@@ -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
+ }
@@ -26,7 +26,6 @@ public class RunWorkerTests
26
26
  ],
27
27
  job: new Job()
28
28
  {
29
- PackageManager = "nuget",
30
29
  Source = new()
31
30
  {
32
31
  Provider = "github",
@@ -161,10 +160,7 @@ public class RunWorkerTests
161
160
  PrTitle = "TODO: title",
162
161
  PrBody = "TODO: body",
163
162
  },
164
- new MarkAsProcessed()
165
- {
166
- BaseCommitSha = "TEST-COMMIT-SHA",
167
- }
163
+ new MarkAsProcessed("TEST-COMMIT-SHA")
168
164
  ]
169
165
  );
170
166
  }
@@ -209,7 +205,6 @@ public class RunWorkerTests
209
205
  ],
210
206
  job: new Job()
211
207
  {
212
- PackageManager = "nuget",
213
208
  Source = new()
214
209
  {
215
210
  Provider = "github",
@@ -249,14 +244,8 @@ public class RunWorkerTests
249
244
  },
250
245
  expectedApiMessages:
251
246
  [
252
- new PrivateSourceAuthenticationFailure()
253
- {
254
- Details = $"({http.BaseUrl.TrimEnd('/')}/index.json)"
255
- },
256
- new MarkAsProcessed()
257
- {
258
- BaseCommitSha = "TEST-COMMIT-SHA",
259
- }
247
+ new PrivateSourceAuthenticationFailure([$"{http.BaseUrl.TrimEnd('/')}/index.json"]),
248
+ new MarkAsProcessed("TEST-COMMIT-SHA")
260
249
  ]
261
250
  );
262
251
  }
@@ -308,7 +297,7 @@ public class RunWorkerTests
308
297
  }
309
298
  }
310
299
 
311
- private static string SerializeObjectAndType(object obj)
300
+ internal static string SerializeObjectAndType(object obj)
312
301
  {
313
302
  return $"{obj.GetType().Name}:{JsonSerializer.Serialize(obj)}";
314
303
  }
@@ -1,4 +1,5 @@
1
1
  using NuGetUpdater.Core.Run;
2
+ using NuGetUpdater.Core.Run.ApiModel;
2
3
 
3
4
  using Xunit;
4
5
 
@@ -57,4 +58,13 @@ public class SerializationTests
57
58
  Assert.Equal("some-org/some-repo", jobWrapper.Job.Source.Repo);
58
59
  Assert.Equal("specific-sdk", jobWrapper.Job.Source.Directory);
59
60
  }
61
+
62
+ [Fact]
63
+ public void SerializeError()
64
+ {
65
+ var error = new JobRepoNotFound("some message");
66
+ var actual = HttpApiHandler.Serialize(error);
67
+ var expected = """{"data":{"error-type":"job_repo_not_found","error-details":{"message":"some message"}}}""";
68
+ Assert.Equal(expected, actual);
69
+ }
60
70
  }
@@ -0,0 +1,225 @@
1
+ using Xunit;
2
+
3
+ namespace NuGetUpdater.Core.Test.Update;
4
+
5
+ public class BindingRedirectsTests
6
+ {
7
+ [Fact]
8
+ public async Task SimpleBindingRedirectIsPerformed()
9
+ {
10
+ await VerifyBindingRedirectsAsync(
11
+ projectContents: """
12
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
13
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
14
+ <PropertyGroup>
15
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
16
+ </PropertyGroup>
17
+ <ItemGroup>
18
+ <None Include="app.config" />
19
+ </ItemGroup>
20
+ <ItemGroup>
21
+ <Reference Include="Some.Package, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
22
+ <HintPath>packages\Some.Package.2.0.0\lib\net45\Some.Package.dll</HintPath>
23
+ <Private>True</Private>
24
+ </Reference>
25
+ </ItemGroup>
26
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
27
+ </Project>
28
+ """,
29
+ configContents: """
30
+ <configuration>
31
+ <runtime>
32
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
33
+ <dependentAssembly>
34
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
35
+ <bindingRedirect oldVersion="0.0.0.0-1.0.0.0" newVersion="1.0.0.0" />
36
+ </dependentAssembly>
37
+ </assemblyBinding>
38
+ </runtime>
39
+ </configuration>
40
+ """,
41
+ updatedPackageName: "Some.Package",
42
+ updatedPackageVersion: "2.0.0",
43
+ expectedConfigContents: """
44
+ <configuration>
45
+ <runtime>
46
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
47
+ <dependentAssembly>
48
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
49
+ <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
50
+ </dependentAssembly>
51
+ </assemblyBinding>
52
+ </runtime>
53
+ </configuration>
54
+ """
55
+ );
56
+ }
57
+
58
+ [Fact]
59
+ public async Task ConfigFileIndentationIsPreserved()
60
+ {
61
+ await VerifyBindingRedirectsAsync(
62
+ projectContents: """
63
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
64
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
65
+ <PropertyGroup>
66
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
67
+ </PropertyGroup>
68
+ <ItemGroup>
69
+ <None Include="app.config" />
70
+ </ItemGroup>
71
+ <ItemGroup>
72
+ <Reference Include="Some.Package, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
73
+ <HintPath>packages\Some.Package.2.0.0\lib\net45\Some.Package.dll</HintPath>
74
+ <Private>True</Private>
75
+ </Reference>
76
+ </ItemGroup>
77
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
78
+ </Project>
79
+ """,
80
+ configContents: """
81
+ <configuration>
82
+ <runtime>
83
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
84
+ <dependentAssembly>
85
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
86
+ <bindingRedirect oldVersion="0.0.0.0-1.0.0.0" newVersion="1.0.0.0" />
87
+ </dependentAssembly>
88
+ </assemblyBinding>
89
+ </runtime>
90
+ </configuration>
91
+ """,
92
+ updatedPackageName: "Some.Package",
93
+ updatedPackageVersion: "2.0.0",
94
+ expectedConfigContents: """
95
+ <configuration>
96
+ <runtime>
97
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
98
+ <dependentAssembly>
99
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
100
+ <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
101
+ </dependentAssembly>
102
+ </assemblyBinding>
103
+ </runtime>
104
+ </configuration>
105
+ """
106
+ );
107
+ }
108
+
109
+ [Fact]
110
+ public async Task NoExtraBindingsAreAdded()
111
+ {
112
+ await VerifyBindingRedirectsAsync(
113
+ projectContents: """
114
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
115
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
116
+ <PropertyGroup>
117
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
118
+ </PropertyGroup>
119
+ <ItemGroup>
120
+ <None Include="app.config" />
121
+ </ItemGroup>
122
+ <ItemGroup>
123
+ <Reference Include="Some.Package, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
124
+ <HintPath>packages\Some.Package.2.0.0\lib\net45\Some.Package.dll</HintPath>
125
+ <Private>True</Private>
126
+ </Reference>
127
+ <Reference Include="Some.Unrelated.Package, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null">
128
+ <HintPath>packages\Some.Unrelated.Package.3.0.0\lib\net45\Some.Package.dll</HintPath>
129
+ <Private>True</Private>
130
+ </Reference>
131
+ </ItemGroup>
132
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
133
+ </Project>
134
+ """,
135
+ configContents: """
136
+ <configuration>
137
+ <runtime>
138
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
139
+ <dependentAssembly>
140
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
141
+ <bindingRedirect oldVersion="0.0.0.0-1.0.0.0" newVersion="1.0.0.0" />
142
+ </dependentAssembly>
143
+ </assemblyBinding>
144
+ </runtime>
145
+ </configuration>
146
+ """,
147
+ updatedPackageName: "Some.Package",
148
+ updatedPackageVersion: "2.0.0",
149
+ expectedConfigContents: """
150
+ <configuration>
151
+ <runtime>
152
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
153
+ <dependentAssembly>
154
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
155
+ <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
156
+ </dependentAssembly>
157
+ </assemblyBinding>
158
+ </runtime>
159
+ </configuration>
160
+ """
161
+ );
162
+ }
163
+
164
+ [Fact]
165
+ public async Task NewBindingIsAdded()
166
+ {
167
+ await VerifyBindingRedirectsAsync(
168
+ projectContents: """
169
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
170
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
171
+ <PropertyGroup>
172
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
173
+ </PropertyGroup>
174
+ <ItemGroup>
175
+ <None Include="app.config" />
176
+ </ItemGroup>
177
+ <ItemGroup>
178
+ <Reference Include="Some.Package, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
179
+ <HintPath>packages\Some.Package.2.0.0\lib\net45\Some.Package.dll</HintPath>
180
+ <Private>True</Private>
181
+ </Reference>
182
+ </ItemGroup>
183
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
184
+ </Project>
185
+ """,
186
+ configContents: """
187
+ <configuration>
188
+ <runtime />
189
+ </configuration>
190
+ """,
191
+ updatedPackageName: "Some.Package",
192
+ updatedPackageVersion: "2.0.0",
193
+ expectedConfigContents: """
194
+ <configuration>
195
+ <runtime>
196
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
197
+ <dependentAssembly>
198
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
199
+ <bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
200
+ </dependentAssembly>
201
+ </assemblyBinding>
202
+ </runtime>
203
+ </configuration>
204
+ """
205
+ );
206
+ }
207
+
208
+ private static async Task VerifyBindingRedirectsAsync(string projectContents, string configContents, string expectedConfigContents, string updatedPackageName, string updatedPackageVersion, string configFileName = "app.config")
209
+ {
210
+ using var tempDir = new TemporaryDirectory();
211
+ var projectFileName = "project.csproj";
212
+ var projectFilePath = Path.Combine(tempDir.DirectoryPath, projectFileName);
213
+ var configFilePath = Path.Combine(tempDir.DirectoryPath, configFileName);
214
+
215
+ await File.WriteAllTextAsync(projectFilePath, projectContents);
216
+ await File.WriteAllTextAsync(configFilePath, configContents);
217
+
218
+ var projectBuildFile = ProjectBuildFile.Open(tempDir.DirectoryPath, projectFilePath);
219
+ await BindingRedirectManager.UpdateBindingRedirectsAsync(projectBuildFile, updatedPackageName, updatedPackageVersion);
220
+
221
+ var actualConfigContents = (await File.ReadAllTextAsync(configFilePath)).Replace("\r", "");
222
+ expectedConfigContents = expectedConfigContents.Replace("\r", "");
223
+ Assert.Equal(expectedConfigContents, actualConfigContents);
224
+ }
225
+ }
@@ -594,6 +594,7 @@ public partial class UpdateWorkerTests
594
594
  [
595
595
  MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "7.0.1", "net45", "7.0.0.0"),
596
596
  MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "13.0.1", "net45", "13.0.0.0"),
597
+ MockNuGetPackage.CreatePackageWithAssembly("Unrelated.Package", "1.2.3", "net45","1.2.0.0"),
597
598
  ],
598
599
  projectContents: """
599
600
  <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
@@ -612,6 +613,10 @@ public partial class UpdateWorkerTests
612
613
  <HintPath>packages\Some.Package.7.0.1\lib\net45\Some.Package.dll</HintPath>
613
614
  <Private>True</Private>
614
615
  </Reference>
616
+ <Reference Include="Unrelated.Package, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null">
617
+ <HintPath>packages\Unrelated.Package.1.2.3\lib\net45\Unrelated.Package.dll</HintPath>
618
+ <Private>True</Private>
619
+ </Reference>
615
620
  </ItemGroup>
616
621
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
617
622
  </Project>
@@ -653,6 +658,107 @@ public partial class UpdateWorkerTests
653
658
  <HintPath>packages\Some.Package.13.0.1\lib\net45\Some.Package.dll</HintPath>
654
659
  <Private>True</Private>
655
660
  </Reference>
661
+ <Reference Include="Unrelated.Package, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null">
662
+ <HintPath>packages\Unrelated.Package.1.2.3\lib\net45\Unrelated.Package.dll</HintPath>
663
+ <Private>True</Private>
664
+ </Reference>
665
+ </ItemGroup>
666
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
667
+ </Project>
668
+ """,
669
+ expectedPackagesConfigContents: """
670
+ <?xml version="1.0" encoding="utf-8"?>
671
+ <packages>
672
+ <package id="Some.Package" version="13.0.1" targetFramework="net45" />
673
+ </packages>
674
+ """,
675
+ additionalFilesExpected:
676
+ [
677
+ ("app.config", """
678
+ <configuration>
679
+ <runtime>
680
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
681
+ <dependentAssembly>
682
+ <assemblyIdentity name="Some.Package" publicKeyToken="null" culture="neutral" />
683
+ <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
684
+ </dependentAssembly>
685
+ </assemblyBinding>
686
+ </runtime>
687
+ </configuration>
688
+ """)
689
+ ]
690
+ );
691
+ }
692
+
693
+ [Fact]
694
+ public async Task BindingRedirectIsAddedForUpdatedPackage()
695
+ {
696
+ await TestUpdateForProject("Some.Package", "7.0.1", "13.0.1",
697
+ packages:
698
+ [
699
+ MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "7.0.1", "net45", "7.0.0.0"),
700
+ MockNuGetPackage.CreatePackageWithAssembly("Some.Package", "13.0.1", "net45", "13.0.0.0"),
701
+ MockNuGetPackage.CreatePackageWithAssembly("Unrelated.Package", "1.2.3", "net45","1.2.0.0"),
702
+ ],
703
+ projectContents: """
704
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
705
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
706
+ <PropertyGroup>
707
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
708
+ </PropertyGroup>
709
+ <ItemGroup>
710
+ <None Include="packages.config" />
711
+ </ItemGroup>
712
+ <ItemGroup>
713
+ <None Include="app.config" />
714
+ </ItemGroup>
715
+ <ItemGroup>
716
+ <Reference Include="Some.Package, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null">
717
+ <HintPath>packages\Some.Package.7.0.1\lib\net45\Some.Package.dll</HintPath>
718
+ <Private>True</Private>
719
+ </Reference>
720
+ <Reference Include="Unrelated.Package, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null">
721
+ <HintPath>packages\Unrelated.Package.1.2.3\lib\net45\Unrelated.Package.dll</HintPath>
722
+ <Private>True</Private>
723
+ </Reference>
724
+ </ItemGroup>
725
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
726
+ </Project>
727
+ """,
728
+ packagesConfigContents: """
729
+ <packages>
730
+ <package id="Some.Package" version="7.0.1" targetFramework="net45" />
731
+ </packages>
732
+ """,
733
+ additionalFiles:
734
+ [
735
+ ("app.config", """
736
+ <configuration>
737
+ <runtime />
738
+ </configuration>
739
+ """)
740
+ ],
741
+ expectedProjectContents: """
742
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
743
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
744
+ <PropertyGroup>
745
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
746
+ </PropertyGroup>
747
+ <ItemGroup>
748
+ <None Include="packages.config" />
749
+ </ItemGroup>
750
+ <ItemGroup>
751
+ <None Include="app.config" />
752
+ </ItemGroup>
753
+ <ItemGroup>
754
+ <Reference Include="Some.Package, Version=13.0.0.0, Culture=neutral, PublicKeyToken=null">
755
+ <HintPath>packages\Some.Package.13.0.1\lib\net45\Some.Package.dll</HintPath>
756
+ <Private>True</Private>
757
+ </Reference>
758
+ <Reference Include="Unrelated.Package, Version=1.2.0.0, Culture=neutral, PublicKeyToken=null">
759
+ <HintPath>packages\Unrelated.Package.1.2.3\lib\net45\Unrelated.Package.dll</HintPath>
760
+ <Private>True</Private>
761
+ </Reference>
656
762
  </ItemGroup>
657
763
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
658
764
  </Project>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.282.0
4
+ version: 0.284.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-24 00:00:00.000000000 Z
11
+ date: 2024-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.282.0
19
+ version: 0.284.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.282.0
26
+ version: 0.284.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -297,6 +297,7 @@ files:
297
297
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs
298
298
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/NuGetUpdater.Cli.Test.csproj
299
299
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs
300
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/CloneCommand.cs
300
301
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/DiscoverCommand.cs
301
302
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/FrameworkCheckCommand.cs
302
303
  - helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs
@@ -310,6 +311,8 @@ files:
310
311
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs
311
312
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/SecurityVulnerabilityExtensionsTests.cs
312
313
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs
314
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs
315
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/TestGitCommandHandler.cs
313
316
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/DependencySolverEnvironment.cs
314
317
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs
315
318
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs
@@ -338,6 +341,7 @@ files:
338
341
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestExtensions.cs
339
342
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs
340
343
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestLogger.cs
344
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/BindingRedirectsTests.cs
341
345
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/ExpectedUpdateOperationResult.cs
342
346
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs
343
347
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs
@@ -366,6 +370,9 @@ files:
366
370
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/SecurityVulnerabilityExtensions.cs
367
371
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs
368
372
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionResult.cs
373
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs
374
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/IGitCommandHandler.cs
375
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/ShellGitCommandHandler.cs
369
376
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Dependency.cs
370
377
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/DependencyType.cs
371
378
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DirectoryPackagesPropsDiscovery.cs
@@ -406,6 +413,7 @@ files:
406
413
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs
407
414
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs
408
415
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobFile.cs
416
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobRepoNotFound.cs
409
417
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs
410
418
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs
411
419
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs
@@ -497,7 +505,7 @@ licenses:
497
505
  - MIT
498
506
  metadata:
499
507
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
500
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.282.0
508
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.284.0
501
509
  post_install_message:
502
510
  rdoc_options: []
503
511
  require_paths: