dependabot-nuget 0.365.0 → 0.366.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e92880fa6ca838ad71e7646048a4e3dfb893a1f2487cf1c10b5d5d75011992c5
4
- data.tar.gz: 12c3e2cad3412e4d05616a9b79af02a6e908ad5adbd478db2bf4110c19bee4c4
3
+ metadata.gz: e0aedc20fa2f1df16323aca062c9c44c6eea43e7765b723de4e95a140684876d
4
+ data.tar.gz: e4b9b5783a76aeb9a28c8af0ade2db202eeb93112a4d49a940860541a433c2fa
5
5
  SHA512:
6
- metadata.gz: 8f12b4d28dff150138a4e8809ea8c396782b3496f17b08e5cf99a464dbba1fad82f6282af9a4611aaec2d2a8458b2d179e32f2b6142ece9190ce5b4718b4bb97
7
- data.tar.gz: 444763884efab01a49b0389e0a7dc402b1ff2e23654f5815daf676073587a0d500cbcb31b4f5a0f5ffb801297f843506629871ec555b320332530918f6d46572
6
+ metadata.gz: 2ac6071c674f86aaf2822d46bb9510d4ae8a9914bea75856864a50087e0aacc4eb36087e3d9403fcba55acc0ad88ae477d289e8134e27958ade18307cd278fe6
7
+ data.tar.gz: 6c0ec49e96823dc4d6dc110ba9aef051031a9adba875242f087eaf8f1484ac49abf59a25e69e72a2f04457398aa32ef3de539c456ced39f677b0b4561ff9185f
@@ -2,6 +2,8 @@ using System.Collections.Immutable;
2
2
  using System.IO.Enumeration;
3
3
  using System.Text.Json;
4
4
 
5
+ using NuGet.Versioning;
6
+
5
7
  namespace NuGetUpdater.Core.Run.ApiModel;
6
8
 
7
9
  public record DependencyGroup
@@ -40,6 +42,32 @@ public class GroupMatcher
40
42
  return isMatch;
41
43
  }
42
44
 
45
+ public bool IsAllowedByVersion(NuGetVersion oldVersion, NuGetVersion newVersion)
46
+ {
47
+ var isMajorBump = newVersion.Major > oldVersion.Major;
48
+ var isMinorBump = newVersion.Major == oldVersion.Major && newVersion.Minor > oldVersion.Minor;
49
+ var isPatchBump = newVersion.Major == oldVersion.Major && newVersion.Minor == oldVersion.Minor && newVersion.Patch > oldVersion.Patch;
50
+
51
+ var allowedUpdateTypes = new HashSet<GroupUpdateType>(UpdateTypes);
52
+
53
+ if (isMajorBump && allowedUpdateTypes.Contains(GroupUpdateType.Major))
54
+ {
55
+ return true;
56
+ }
57
+
58
+ if (isMinorBump && allowedUpdateTypes.Contains(GroupUpdateType.Minor))
59
+ {
60
+ return true;
61
+ }
62
+
63
+ if (isPatchBump && allowedUpdateTypes.Contains(GroupUpdateType.Patch))
64
+ {
65
+ return true;
66
+ }
67
+
68
+ return false;
69
+ }
70
+
43
71
  public static GroupMatcher FromRules(Dictionary<string, object> rules)
44
72
  {
45
73
  var patterns = GetStringArray(rules, "patterns", ["*"]); // default to matching everything unless explicitly excluded
@@ -157,7 +157,7 @@ internal class CreateSecurityUpdatePullRequestHandler : IUpdateHandler
157
157
  }
158
158
  }
159
159
 
160
- if (updatedDependencyFiles.Length > 0)
160
+ if (updateOperationsPerformed.Count > 0 && updatedDependencyFiles.Length > 0)
161
161
  {
162
162
  var commitMessage = PullRequestTextGenerator.GetPullRequestCommitMessage(job, [.. updateOperationsPerformed], null);
163
163
  var prTitle = PullRequestTextGenerator.GetPullRequestTitle(job, [.. updateOperationsPerformed], null);
@@ -1,5 +1,8 @@
1
1
  using System.Collections.Immutable;
2
2
 
3
+ using NuGet.Versioning;
4
+
5
+ using NuGetUpdater.Core.Analyze;
3
6
  using NuGetUpdater.Core.Discover;
4
7
  using NuGetUpdater.Core.Run.ApiModel;
5
8
  using NuGetUpdater.Core.Updater;
@@ -106,6 +109,13 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
106
109
  continue;
107
110
  }
108
111
 
112
+ var isUpdateAllowed = groupMatcher.IsAllowedByVersion(NuGetVersion.Parse(dependency.Version!), NuGetVersion.Parse(analysisResult.UpdatedVersion));
113
+ if (!isUpdateAllowed)
114
+ {
115
+ logger.Info($"Dependency {dependency.Name} skipped for group {group.Name} because update type was not allowed.");
116
+ continue;
117
+ }
118
+
109
119
  var projectDiscovery = discoveryResult.GetProjectDiscoveryFromPath(projectPath);
110
120
  var updaterResult = await updaterWorker.RunAsync(repoContentsPath.FullName, projectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, dependency.IsTransitive);
111
121
  if (updaterResult.Error is not null)
@@ -204,15 +214,6 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
204
214
  continue;
205
215
  }
206
216
 
207
- var matchingGroups = job.DependencyGroups
208
- .Where(group => group.GetGroupMatcher().IsMatch(dependency.Name))
209
- .ToImmutableArray();
210
- if (matchingGroups.Length > 0)
211
- {
212
- logger.Info($"Dependency {dependency.Name} skipped for ungrouped updates because it's a member of the following groups: {string.Join(", ", matchingGroups.Select(group => group.Name))}");
213
- continue;
214
- }
215
-
216
217
  var dependencyInfo = RunWorker.GetDependencyInfo(job, dependency, groupMatchers: [], allowCooldown: true);
217
218
  var analysisResult = await analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
218
219
  if (analysisResult.Error is not null)
@@ -228,6 +229,12 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
228
229
  continue;
229
230
  }
230
231
 
232
+ var isSkipped = IsUngroupedDependencySkipped(dependency, analysisResult, job.DependencyGroups, logger);
233
+ if (isSkipped)
234
+ {
235
+ continue;
236
+ }
237
+
231
238
  var projectDiscovery = discoveryResult.GetProjectDiscoveryFromPath(projectPath);
232
239
  var updaterResult = await updaterWorker.RunAsync(repoContentsPath.FullName, projectPath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, dependency.IsTransitive);
233
240
  if (updaterResult.Error is not null)
@@ -293,4 +300,29 @@ internal class GroupUpdateAllVersionsHandler : IUpdateHandler
293
300
  .ToImmutableArray();
294
301
  return updateOperationsToPerformByDependency;
295
302
  }
303
+
304
+ internal static bool IsUngroupedDependencySkipped(Dependency dependency, AnalysisResult dependencyAnalysis, ImmutableArray<DependencyGroup> dependencyGroups, ILogger logger)
305
+ {
306
+ var matcherGroups = dependencyGroups
307
+ .Select(group => (group.Name, Matcher: group.GetGroupMatcher()))
308
+ .Where(pair => pair.Matcher.IsMatch(dependency.Name))
309
+ .ToImmutableArray();
310
+ if (matcherGroups.Length > 0)
311
+ {
312
+ // update matches a group by name
313
+ // if any group allows the proposed version range, then it's not allowed in an ungrouped update
314
+ var oldVersion = NuGetVersion.Parse(dependency.Version!);
315
+ var newVersion = NuGetVersion.Parse(dependencyAnalysis.UpdatedVersion);
316
+ var matcherGroupsAllowingVersionRange = matcherGroups
317
+ .Where(pair => pair.Matcher.IsAllowedByVersion(oldVersion, newVersion))
318
+ .ToImmutableArray();
319
+ if (matcherGroupsAllowingVersionRange.Length > 0)
320
+ {
321
+ logger.Info($"Dependency {dependency.Name} skipped for ungrouped updates because it's a member of the following groups: {string.Join(", ", matcherGroupsAllowingVersionRange.Select(pair => pair.Name))}");
322
+ return true;
323
+ }
324
+ }
325
+
326
+ return false;
327
+ }
296
328
  }
@@ -728,6 +728,7 @@ internal static partial class MSBuildHelper
728
728
  {
729
729
  new Regex(@"\nAn error occurred while reading file '(?<FilePath>[^']+)': (?<Message>[^\n]*)\n"),
730
730
  new Regex(@"NuGet\.Config is not valid XML\. Path: '(?<FilePath>[^']+)'\.\n\s*(?<Message>[^\n]*)(\n|$)"),
731
+ new Regex(@"Error parsing packages\.config file at (?<FilePath>[^:]+): (?<Message>[^\n]*)\n"),
731
732
  };
732
733
  var match = patterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
733
734
  if (match is not null)
@@ -763,4 +763,97 @@ public class CreateSecurityUpdatePullRequestHandlerTests : UpdateHandlersTestsBa
763
763
  ]
764
764
  );
765
765
  }
766
+
767
+ [Fact]
768
+ public async Task ErrantFileUpdatesDoNotCauseCallToCreatePullRequest()
769
+ {
770
+ // if an external tool inadvertently updates files on disk without reporting any update operations, don't try
771
+ // to create a PR
772
+ await TestAsync(
773
+ job: new Job()
774
+ {
775
+ Dependencies = ["Some.Dependency"],
776
+ SecurityAdvisories = [new() { DependencyName = "Some.Dependency", AffectedVersions = [Requirement.Parse("= 1.0.0")] }],
777
+ SecurityUpdatesOnly = true,
778
+ Source = CreateJobSource("/src"),
779
+ },
780
+ files: [
781
+ ("src/project.csproj", "initial project contents"),
782
+ ("src/packages.config", "initial packages contents"),
783
+ ],
784
+ discoveryWorker: TestDiscoveryWorker.FromResults(
785
+ ("/src", new WorkspaceDiscoveryResult()
786
+ {
787
+ Path = "/src",
788
+ Projects = [
789
+ new()
790
+ {
791
+ FilePath = "project.csproj",
792
+ Dependencies = [
793
+ new("Some.Dependency", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net9.0"]),
794
+ ],
795
+ ImportedFiles = [],
796
+ AdditionalFiles = ["packages.config"],
797
+ }
798
+ ],
799
+ })
800
+ ),
801
+ analyzeWorker: new TestAnalyzeWorker(async input =>
802
+ {
803
+ var repoRoot = input.Item1;
804
+ var discovery = input.Item2;
805
+ var dependencyInfo = input.Item3;
806
+ if (dependencyInfo.Name != "Some.Dependency")
807
+ {
808
+ throw new NotImplementedException($"Test didn't expect to update dependency {dependencyInfo.Name}");
809
+ }
810
+
811
+ // no update possible but a file was touched on disk
812
+ var projectPath = Path.Join(repoRoot, discovery.Path, discovery.Projects.Single().FilePath);
813
+ var packagesConfigPath = Path.Join(Path.GetDirectoryName(projectPath), "packages.config");
814
+ await File.WriteAllTextAsync(packagesConfigPath, "updated packages contents");
815
+
816
+ return new AnalysisResult()
817
+ {
818
+ CanUpdate = false,
819
+ UpdatedVersion = "1.0.0",
820
+ UpdatedDependencies = [],
821
+ };
822
+ }),
823
+ updaterWorker: new TestUpdaterWorker(async input =>
824
+ {
825
+ return new UpdateOperationResult()
826
+ {
827
+ UpdateOperations = [],
828
+ };
829
+ }),
830
+ expectedUpdateHandler: CreateSecurityUpdatePullRequestHandler.Instance,
831
+ expectedApiMessages: [
832
+ new UpdatedDependencyList()
833
+ {
834
+ Dependencies = [
835
+ new()
836
+ {
837
+ Name = "Some.Dependency",
838
+ Version = "1.0.0",
839
+ Requirements = [
840
+ new() { Requirement = "1.0.0", File = "/src/project.csproj", Groups = ["dependencies"] },
841
+ ],
842
+ },
843
+ ],
844
+ DependencyFiles = ["/src/packages.config", "/src/project.csproj"],
845
+ },
846
+ new IncrementMetric()
847
+ {
848
+ Metric = "updater.started",
849
+ Tags = new()
850
+ {
851
+ ["operation"] = "create_security_pr",
852
+ }
853
+ },
854
+ new SecurityUpdateNotFound("Some.Dependency", "1.0.0"),
855
+ new MarkAsProcessed("TEST-COMMIT-SHA"),
856
+ ]
857
+ );
858
+ }
766
859
  }
@@ -1370,4 +1370,150 @@ public class GroupUpdateAllVersionsHandlerTests : UpdateHandlersTestsBase
1370
1370
  ]
1371
1371
  );
1372
1372
  }
1373
+
1374
+ [Fact]
1375
+ public async Task UngroupedPullRequestCanBeCreatedIfGroupAppliesToNonMatchedTypes()
1376
+ {
1377
+ // group only applies to minor and patch updates, but a major update is requested and gets generated separately
1378
+ await TestAsync(
1379
+ job: new Job()
1380
+ {
1381
+ Source = CreateJobSource("/src"),
1382
+ DependencyGroups = [new()
1383
+ {
1384
+ Name = "test-group",
1385
+ Rules = new()
1386
+ {
1387
+ ["patterns"] = new[] { "Some.Dependency" },
1388
+ ["update-types"] = new[] { "minor", "patch" }
1389
+ },
1390
+ }]
1391
+ },
1392
+ files: [
1393
+ ("src/project.csproj", "initial contents"),
1394
+ ],
1395
+ discoveryWorker: TestDiscoveryWorker.FromResults(
1396
+ ("/src", new WorkspaceDiscoveryResult()
1397
+ {
1398
+ Path = "/src",
1399
+ Projects = [
1400
+ new()
1401
+ {
1402
+ FilePath = "project.csproj",
1403
+ Dependencies = [
1404
+ new("Some.Dependency", "1.0.0", DependencyType.PackageReference, TargetFrameworks: ["net9.0"]),
1405
+ ],
1406
+ ImportedFiles = [],
1407
+ AdditionalFiles = [],
1408
+ }
1409
+ ],
1410
+ })
1411
+ ),
1412
+ analyzeWorker: new TestAnalyzeWorker(input =>
1413
+ {
1414
+ var repoRoot = input.Item1;
1415
+ var discovery = input.Item2;
1416
+ var dependencyInfo = input.Item3;
1417
+ var newVersion = dependencyInfo.Name switch
1418
+ {
1419
+ "Some.Dependency" => "2.0.0",
1420
+ _ => throw new NotImplementedException($"Test didn't expect to update dependency {dependencyInfo.Name}"),
1421
+ };
1422
+ return Task.FromResult(new AnalysisResult()
1423
+ {
1424
+ CanUpdate = true,
1425
+ UpdatedVersion = newVersion,
1426
+ UpdatedDependencies = [],
1427
+ });
1428
+ }),
1429
+ updaterWorker: new TestUpdaterWorker(async input =>
1430
+ {
1431
+ var repoRoot = input.Item1;
1432
+ var workspacePath = input.Item2;
1433
+ var dependencyName = input.Item3;
1434
+ var previousVersion = input.Item4;
1435
+ var newVersion = input.Item5;
1436
+ var isTransitive = input.Item6;
1437
+
1438
+ await File.WriteAllTextAsync(Path.Join(repoRoot, workspacePath), "updated contents");
1439
+
1440
+ return new UpdateOperationResult()
1441
+ {
1442
+ UpdateOperations = [new DirectUpdate() { DependencyName = dependencyName, NewVersion = NuGetVersion.Parse(newVersion), UpdatedFiles = [workspacePath] }],
1443
+ };
1444
+ }),
1445
+ expectedUpdateHandler: GroupUpdateAllVersionsHandler.Instance,
1446
+ expectedApiMessages: [
1447
+ new IncrementMetric()
1448
+ {
1449
+ Metric = "updater.started",
1450
+ Tags = new()
1451
+ {
1452
+ ["operation"] = "group_update_all_versions",
1453
+ }
1454
+ },
1455
+ // discovery from group updater
1456
+ new UpdatedDependencyList()
1457
+ {
1458
+ Dependencies = [
1459
+ new()
1460
+ {
1461
+ Name = "Some.Dependency",
1462
+ Version = "1.0.0",
1463
+ Requirements = [
1464
+ new() { Requirement = "1.0.0", File = "/src/project.csproj", Groups = ["dependencies"] },
1465
+ ],
1466
+ },
1467
+ ],
1468
+ DependencyFiles = ["/src/project.csproj"],
1469
+ },
1470
+ // discovery from ungrouped updater
1471
+ new UpdatedDependencyList()
1472
+ {
1473
+ Dependencies = [
1474
+ new()
1475
+ {
1476
+ Name = "Some.Dependency",
1477
+ Version = "1.0.0",
1478
+ Requirements = [
1479
+ new() { Requirement = "1.0.0", File = "/src/project.csproj", Groups = ["dependencies"] },
1480
+ ],
1481
+ },
1482
+ ],
1483
+ DependencyFiles = ["/src/project.csproj"],
1484
+ },
1485
+ new CreatePullRequest()
1486
+ {
1487
+ Dependencies = [
1488
+ new()
1489
+ {
1490
+ Name = "Some.Dependency",
1491
+ Version = "2.0.0",
1492
+ Requirements = [
1493
+ new() { Requirement = "2.0.0", File = "/src/project.csproj", Groups = ["dependencies"], Source = new() { SourceUrl = null } },
1494
+ ],
1495
+ PreviousVersion = "1.0.0",
1496
+ PreviousRequirements = [
1497
+ new() { Requirement = "1.0.0", File = "/src/project.csproj", Groups = ["dependencies"] },
1498
+ ],
1499
+ },
1500
+ ],
1501
+ UpdatedDependencyFiles = [
1502
+ new()
1503
+ {
1504
+ Directory = "/src",
1505
+ Name = "project.csproj",
1506
+ Content = "updated contents",
1507
+ },
1508
+ ],
1509
+ BaseCommitSha = "TEST-COMMIT-SHA",
1510
+ CommitMessage = EndToEndTests.TestPullRequestCommitMessage,
1511
+ PrTitle = EndToEndTests.TestPullRequestTitle,
1512
+ PrBody = EndToEndTests.TestPullRequestBody,
1513
+ DependencyGroup = null,
1514
+ },
1515
+ new MarkAsProcessed("TEST-COMMIT-SHA"),
1516
+ ]
1517
+ );
1518
+ }
1373
1519
  }
@@ -634,5 +634,17 @@ public class MSBuildHelperTests : TestBase
634
634
  // expectedError
635
635
  new UnknownError(new Exception("Multiple project files found for single packages.config"), "TEST-JOB-ID"),
636
636
  ];
637
+
638
+ yield return
639
+ [
640
+ // output
641
+ """
642
+ Error parsing packages.config file at /path/to/packages.config: Unexpected XML declaration. The XML declaration must be the first node in the document, and no whitespace characters are allowed to appear before it. Line 1, position 5.
643
+
644
+ ^^^ this blank line is necessary to force a newline at the end of the output
645
+ """,
646
+ // expectedError
647
+ new DependencyFileNotParseable("/path/to/packages.config", "Unexpected XML declaration. The XML declaration must be the first node in the document, and no whitespace characters are allowed to appear before it. Line 1, position 5.")
648
+ ];
637
649
  }
638
650
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.365.0
4
+ version: 0.366.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.365.0
18
+ version: 0.366.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.365.0
25
+ version: 0.366.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -553,7 +553,7 @@ licenses:
553
553
  - MIT
554
554
  metadata:
555
555
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
556
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.365.0
556
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.366.0
557
557
  rdoc_options: []
558
558
  require_paths:
559
559
  - lib