diff --git a/NGitLab.Mock.Tests/MergeRequestsMockTests.cs b/NGitLab.Mock.Tests/MergeRequestsMockTests.cs index e1947636..74419282 100644 --- a/NGitLab.Mock.Tests/MergeRequestsMockTests.cs +++ b/NGitLab.Mock.Tests/MergeRequestsMockTests.cs @@ -437,4 +437,57 @@ public void Test_create_merge_request_without_target_project_id() Assert.That(mergeRequest, Is.Not.Null); } + + [Test] + public void Test_create_merge_request_with_merge_after() + { + using var server = new GitLabConfig() + .WithUser("User1", isDefault: true) + .WithProject("Test", addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Second commit", sourceBranch: "feature-branch", configure: commit => commit + .WithFile("file.txt", "content"))) + .BuildServer(); + + var client = server.CreateClient(); + var mergeAfter = new DateTime(2030, 1, 2, 3, 4, 5, DateTimeKind.Utc); + + var mergeRequest = client + .GetMergeRequest(1) + .Create(new MergeRequestCreate + { + SourceBranch = "feature-branch", + TargetBranch = "main", + Title = "Merge request 1", + MergeAfter = mergeAfter, + }); + + Assert.That(mergeRequest.MergeAfter, Is.EqualTo(mergeAfter)); + } + + [Test] + public void Test_update_merge_request_with_merge_after() + { + using var server = new GitLabConfig() + .WithUser("User1", isDefault: true) + .WithProject("Test", addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Second commit", sourceBranch: "feature-branch", configure: commit => commit + .WithFile("file.txt", "content"))) + .BuildServer(); + + var mrClient = server.CreateClient().GetMergeRequest(1); + var mergeRequest = mrClient.Create(new MergeRequestCreate + { + SourceBranch = "feature-branch", + TargetBranch = "main", + Title = "Merge request 1", + }); + var mergeAfter = new DateTime(2030, 1, 2, 3, 4, 5, DateTimeKind.Utc); + + var updated = mrClient.Update(mergeRequest.Iid, new MergeRequestUpdate { MergeAfter = mergeAfter }); + + Assert.That(mergeRequest.MergeAfter, Is.Null); + Assert.That(updated.MergeAfter, Is.EqualTo(mergeAfter)); + } } diff --git a/NGitLab.Mock/Clients/MergeRequestClient.cs b/NGitLab.Mock/Clients/MergeRequestClient.cs index 9e662619..6670dc05 100644 --- a/NGitLab.Mock/Clients/MergeRequestClient.cs +++ b/NGitLab.Mock/Clients/MergeRequestClient.cs @@ -345,6 +345,7 @@ public Models.MergeRequest Create(MergeRequestCreate mergeRequestCreate) mergeRequest.Description = mergeRequestCreate.Description; mergeRequest.ShouldRemoveSourceBranch = mergeRequestCreate.RemoveSourceBranch; mergeRequest.Squash = mergeRequestCreate.Squash; + mergeRequest.MergeAfter = mergeRequestCreate.MergeAfter; SetLabels(mergeRequest, mergeRequestCreate.Labels, labelsToAdd: null, labelsToRemove: null); return mergeRequest.ToMergeRequestClient(); @@ -714,6 +715,11 @@ public Models.MergeRequest Update(long mergeRequestIid, MergeRequestUpdate merge mergeRequest.Title = mergeRequestUpdate.Title; } + if (mergeRequestUpdate.MergeAfter != null) + { + mergeRequest.MergeAfter = mergeRequestUpdate.MergeAfter; + } + SetLabels(mergeRequest, mergeRequestUpdate.Labels, mergeRequestUpdate.AddLabels, mergeRequestUpdate.RemoveLabels); mergeRequest.UpdatedAt = DateTimeOffset.UtcNow; diff --git a/NGitLab.Mock/MergeRequest.cs b/NGitLab.Mock/MergeRequest.cs index 351ccdb5..642d5735 100644 --- a/NGitLab.Mock/MergeRequest.cs +++ b/NGitLab.Mock/MergeRequest.cs @@ -124,6 +124,8 @@ public IReadOnlyList Versions public DateTimeOffset? ClosedAt { get; set; } + public DateTimeOffset? MergeAfter { get; set; } + public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; public Sha1 Sha { get; private set; } @@ -299,6 +301,7 @@ internal Models.MergeRequest ToMergeRequestClient() UpdatedAt = UpdatedAt.UtcDateTime, MergedAt = MergedAt?.UtcDateTime, ClosedAt = ClosedAt?.UtcDateTime, + MergeAfter = MergeAfter?.UtcDateTime, Description = Description, Draft = Draft, Id = Id, diff --git a/NGitLab.Mock/PublicAPI.Unshipped.txt b/NGitLab.Mock/PublicAPI.Unshipped.txt index 77630c8b..b59cd90e 100644 --- a/NGitLab.Mock/PublicAPI.Unshipped.txt +++ b/NGitLab.Mock/PublicAPI.Unshipped.txt @@ -671,6 +671,8 @@ NGitLab.Mock.MergeRequest.HeadSha.get -> string NGitLab.Mock.MergeRequest.Id.get -> long NGitLab.Mock.MergeRequest.Iid.get -> long NGitLab.Mock.MergeRequest.Labels.get -> System.Collections.Generic.IList +NGitLab.Mock.MergeRequest.MergeAfter.get -> System.DateTimeOffset? +NGitLab.Mock.MergeRequest.MergeAfter.set -> void NGitLab.Mock.MergeRequest.MergeCommitSha.get -> NGitLab.Sha1? NGitLab.Mock.MergeRequest.MergeCommitSha.set -> void NGitLab.Mock.MergeRequest.MergedAt.get -> System.DateTimeOffset? diff --git a/NGitLab.Tests/Docker/GitLabTestContext.cs b/NGitLab.Tests/Docker/GitLabTestContext.cs index f5209495..9b20749c 100644 --- a/NGitLab.Tests/Docker/GitLabTestContext.cs +++ b/NGitLab.Tests/Docker/GitLabTestContext.cs @@ -269,6 +269,12 @@ public Group CreateSubgroup(long parentGroupId, string slug, string name = null, configure?.Invoke(mergeRequestCreate); var mr = client.GetMergeRequest(project.Id).Create(mergeRequestCreate); + // A merge request with a merge_after date more than a minute away will not become mergeable within the wait below. + if (mergeRequestCreate.MergeAfter?.ToUniversalTime() > DateTime.UtcNow.AddMinutes(1)) + { + return (project, mr); + } + // GitLab computes diff data and merge status asynchronously after MR creation. // Wait until the transient states resolve so callers can immediately query diffs or approvals. var mrClient = client.GetMergeRequest(project.Id); diff --git a/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs b/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs index ec9a050b..866a31ee 100644 --- a/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs +++ b/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs @@ -282,6 +282,35 @@ public async Task Test_cancel_merge_when_pipeline_succeeds() AcceptAndCancelMergeRequest(mergeRequestClient, mergeRequest); } + [Test] + [NGitLabRetry] + public async Task Test_create_merge_request_with_merge_after() + { + using var context = await GitLabTestContext.CreateAsync(); + context.IgnoreTestIfGitLabVersionOutOfRange(VersionRange.Parse("[17.8,)")); + var mergeAfter = CreateMergeAfter(); + + var (_, mergeRequest) = await context.CreateMergeRequestAsync(mr => mr.MergeAfter = mergeAfter); + + Assert.That(mergeRequest.MergeAfter?.ToUniversalTime(), Is.EqualTo(mergeAfter)); + } + + [Test] + [NGitLabRetry] + public async Task Test_update_merge_request_with_merge_after() + { + using var context = await GitLabTestContext.CreateAsync(); + context.IgnoreTestIfGitLabVersionOutOfRange(VersionRange.Parse("[17.8,)")); + var (project, mergeRequest) = await context.CreateMergeRequestAsync(); + var mergeRequestClient = context.Client.GetMergeRequest(project.Id); + var mergeAfter = CreateMergeAfter(); + + var updated = mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate { MergeAfter = mergeAfter }); + + Assert.That(mergeRequest.MergeAfter, Is.Null); + Assert.That(updated.MergeAfter?.ToUniversalTime(), Is.EqualTo(mergeAfter)); + } + [Test] [NGitLabRetry] public async Task Test_merge_request_head_pipeline() @@ -297,6 +326,13 @@ public async Task Test_merge_request_head_pipeline() Assert.That(mergeRequest.HeadPipeline?.ProjectId, Is.EqualTo(project.Id)); } + private static DateTime CreateMergeAfter() + { + // GitLab returns the timestamp with millisecond precision. Use whole seconds so the value round-trips. + var inOneWeek = DateTime.UtcNow.AddDays(7); + return new DateTime(inOneWeek.Year, inOneWeek.Month, inOneWeek.Day, inOneWeek.Hour, inOneWeek.Minute, inOneWeek.Second, DateTimeKind.Utc); + } + private static void ListMergeRequest(IMergeRequestClient mergeRequestClient, MergeRequest mergeRequest) { Assert.That(mergeRequestClient.All.Any(x => x.Id == mergeRequest.Id), Is.True, "Test 'All' accessor returns the merge request"); diff --git a/NGitLab/Models/MergeRequest.cs b/NGitLab/Models/MergeRequest.cs index c63d7376..21d9982d 100644 --- a/NGitLab/Models/MergeRequest.cs +++ b/NGitLab/Models/MergeRequest.cs @@ -148,6 +148,9 @@ public class MergeRequest [JsonPropertyName("detailed_merge_status")] public DynamicEnum DetailedMergeStatus { get; set; } + [JsonPropertyName("merge_after")] + public DateTime? MergeAfter { get; set; } + [JsonPropertyName("merge_error")] public string MergeError { get; set; } diff --git a/NGitLab/Models/MergeRequestCreate.cs b/NGitLab/Models/MergeRequestCreate.cs index 0fa39ecc..be2ecbed 100644 --- a/NGitLab/Models/MergeRequestCreate.cs +++ b/NGitLab/Models/MergeRequestCreate.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System; +using System.Text.Json.Serialization; namespace NGitLab.Models; @@ -42,4 +43,7 @@ public class MergeRequestCreate [JsonPropertyName("allow_collaboration")] public bool? AllowCollaboration { get; set; } + + [JsonPropertyName("merge_after")] + public DateTime? MergeAfter { get; set; } } diff --git a/NGitLab/Models/MergeRequestUpdate.cs b/NGitLab/Models/MergeRequestUpdate.cs index 1746b593..8ef11201 100644 --- a/NGitLab/Models/MergeRequestUpdate.cs +++ b/NGitLab/Models/MergeRequestUpdate.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System; +using System.Text.Json.Serialization; namespace NGitLab.Models; @@ -51,6 +52,9 @@ public class MergeRequestUpdate [JsonPropertyName("squash_on_merge")] public bool? SquashOnMerge { get; set; } + + [JsonPropertyName("merge_after")] + public DateTime? MergeAfter { get; set; } } public class MergeRequestUpdateState diff --git a/NGitLab/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 17cdfba0..627a604f 100644 --- a/NGitLab/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -2892,6 +2892,8 @@ NGitLab.Models.MergeRequest.Iid.get -> long NGitLab.Models.MergeRequest.Iid.set -> void NGitLab.Models.MergeRequest.Labels.get -> string[] NGitLab.Models.MergeRequest.Labels.set -> void +NGitLab.Models.MergeRequest.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequest.MergeAfter.set -> void NGitLab.Models.MergeRequest.MergeCommitSha.get -> string NGitLab.Models.MergeRequest.MergeCommitSha.set -> void NGitLab.Models.MergeRequest.MergedAt.get -> System.DateTime? @@ -3058,6 +3060,8 @@ NGitLab.Models.MergeRequestCreate.Description.get -> string NGitLab.Models.MergeRequestCreate.Description.set -> void NGitLab.Models.MergeRequestCreate.Labels.get -> string NGitLab.Models.MergeRequestCreate.Labels.set -> void +NGitLab.Models.MergeRequestCreate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestCreate.MergeAfter.set -> void NGitLab.Models.MergeRequestCreate.MergeRequestCreate() -> void NGitLab.Models.MergeRequestCreate.MilestoneId.get -> long? NGitLab.Models.MergeRequestCreate.MilestoneId.set -> void @@ -3177,6 +3181,8 @@ NGitLab.Models.MergeRequestUpdate.Description.get -> string NGitLab.Models.MergeRequestUpdate.Description.set -> void NGitLab.Models.MergeRequestUpdate.Labels.get -> string NGitLab.Models.MergeRequestUpdate.Labels.set -> void +NGitLab.Models.MergeRequestUpdate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestUpdate.MergeAfter.set -> void NGitLab.Models.MergeRequestUpdate.MergeRequestUpdate() -> void NGitLab.Models.MergeRequestUpdate.MilestoneId.get -> long? NGitLab.Models.MergeRequestUpdate.MilestoneId.set -> void diff --git a/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt index 84431238..3bfbea38 100644 --- a/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -2893,6 +2893,8 @@ NGitLab.Models.MergeRequest.Iid.get -> long NGitLab.Models.MergeRequest.Iid.set -> void NGitLab.Models.MergeRequest.Labels.get -> string[] NGitLab.Models.MergeRequest.Labels.set -> void +NGitLab.Models.MergeRequest.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequest.MergeAfter.set -> void NGitLab.Models.MergeRequest.MergeCommitSha.get -> string NGitLab.Models.MergeRequest.MergeCommitSha.set -> void NGitLab.Models.MergeRequest.MergedAt.get -> System.DateTime? @@ -3059,6 +3061,8 @@ NGitLab.Models.MergeRequestCreate.Description.get -> string NGitLab.Models.MergeRequestCreate.Description.set -> void NGitLab.Models.MergeRequestCreate.Labels.get -> string NGitLab.Models.MergeRequestCreate.Labels.set -> void +NGitLab.Models.MergeRequestCreate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestCreate.MergeAfter.set -> void NGitLab.Models.MergeRequestCreate.MergeRequestCreate() -> void NGitLab.Models.MergeRequestCreate.MilestoneId.get -> long? NGitLab.Models.MergeRequestCreate.MilestoneId.set -> void @@ -3178,6 +3182,8 @@ NGitLab.Models.MergeRequestUpdate.Description.get -> string NGitLab.Models.MergeRequestUpdate.Description.set -> void NGitLab.Models.MergeRequestUpdate.Labels.get -> string NGitLab.Models.MergeRequestUpdate.Labels.set -> void +NGitLab.Models.MergeRequestUpdate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestUpdate.MergeAfter.set -> void NGitLab.Models.MergeRequestUpdate.MergeRequestUpdate() -> void NGitLab.Models.MergeRequestUpdate.MilestoneId.get -> long? NGitLab.Models.MergeRequestUpdate.MilestoneId.set -> void diff --git a/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 17cdfba0..627a604f 100644 --- a/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -2892,6 +2892,8 @@ NGitLab.Models.MergeRequest.Iid.get -> long NGitLab.Models.MergeRequest.Iid.set -> void NGitLab.Models.MergeRequest.Labels.get -> string[] NGitLab.Models.MergeRequest.Labels.set -> void +NGitLab.Models.MergeRequest.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequest.MergeAfter.set -> void NGitLab.Models.MergeRequest.MergeCommitSha.get -> string NGitLab.Models.MergeRequest.MergeCommitSha.set -> void NGitLab.Models.MergeRequest.MergedAt.get -> System.DateTime? @@ -3058,6 +3060,8 @@ NGitLab.Models.MergeRequestCreate.Description.get -> string NGitLab.Models.MergeRequestCreate.Description.set -> void NGitLab.Models.MergeRequestCreate.Labels.get -> string NGitLab.Models.MergeRequestCreate.Labels.set -> void +NGitLab.Models.MergeRequestCreate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestCreate.MergeAfter.set -> void NGitLab.Models.MergeRequestCreate.MergeRequestCreate() -> void NGitLab.Models.MergeRequestCreate.MilestoneId.get -> long? NGitLab.Models.MergeRequestCreate.MilestoneId.set -> void @@ -3177,6 +3181,8 @@ NGitLab.Models.MergeRequestUpdate.Description.get -> string NGitLab.Models.MergeRequestUpdate.Description.set -> void NGitLab.Models.MergeRequestUpdate.Labels.get -> string NGitLab.Models.MergeRequestUpdate.Labels.set -> void +NGitLab.Models.MergeRequestUpdate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestUpdate.MergeAfter.set -> void NGitLab.Models.MergeRequestUpdate.MergeRequestUpdate() -> void NGitLab.Models.MergeRequestUpdate.MilestoneId.get -> long? NGitLab.Models.MergeRequestUpdate.MilestoneId.set -> void diff --git a/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 84431238..3bfbea38 100644 --- a/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -2893,6 +2893,8 @@ NGitLab.Models.MergeRequest.Iid.get -> long NGitLab.Models.MergeRequest.Iid.set -> void NGitLab.Models.MergeRequest.Labels.get -> string[] NGitLab.Models.MergeRequest.Labels.set -> void +NGitLab.Models.MergeRequest.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequest.MergeAfter.set -> void NGitLab.Models.MergeRequest.MergeCommitSha.get -> string NGitLab.Models.MergeRequest.MergeCommitSha.set -> void NGitLab.Models.MergeRequest.MergedAt.get -> System.DateTime? @@ -3059,6 +3061,8 @@ NGitLab.Models.MergeRequestCreate.Description.get -> string NGitLab.Models.MergeRequestCreate.Description.set -> void NGitLab.Models.MergeRequestCreate.Labels.get -> string NGitLab.Models.MergeRequestCreate.Labels.set -> void +NGitLab.Models.MergeRequestCreate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestCreate.MergeAfter.set -> void NGitLab.Models.MergeRequestCreate.MergeRequestCreate() -> void NGitLab.Models.MergeRequestCreate.MilestoneId.get -> long? NGitLab.Models.MergeRequestCreate.MilestoneId.set -> void @@ -3178,6 +3182,8 @@ NGitLab.Models.MergeRequestUpdate.Description.get -> string NGitLab.Models.MergeRequestUpdate.Description.set -> void NGitLab.Models.MergeRequestUpdate.Labels.get -> string NGitLab.Models.MergeRequestUpdate.Labels.set -> void +NGitLab.Models.MergeRequestUpdate.MergeAfter.get -> System.DateTime? +NGitLab.Models.MergeRequestUpdate.MergeAfter.set -> void NGitLab.Models.MergeRequestUpdate.MergeRequestUpdate() -> void NGitLab.Models.MergeRequestUpdate.MilestoneId.get -> long? NGitLab.Models.MergeRequestUpdate.MilestoneId.set -> void