diff --git a/NGitLab.Mock/Clients/CommitClient.cs b/NGitLab.Mock/Clients/CommitClient.cs index 4302979f..8b0c248b 100644 --- a/NGitLab.Mock/Clients/CommitClient.cs +++ b/NGitLab.Mock/Clients/CommitClient.cs @@ -67,4 +67,9 @@ public JobStatus GetJobStatus(string branchName) return GitLabCollectionResponse.Create(relatedMerqueRequests); } } + + public Commit Revert(CommitRevert revert) + { + throw new NotImplementedException(); + } } diff --git a/NGitLab.Tests/CommitsTests.cs b/NGitLab.Tests/CommitsTests.cs index 2e4ebda4..741c1336 100644 --- a/NGitLab.Tests/CommitsTests.cs +++ b/NGitLab.Tests/CommitsTests.cs @@ -119,6 +119,54 @@ public async Task Test_can_cherry_pick_commit() Assert.That(latestCommit.Id, Is.EqualTo(cherryPickedCommit.Id)); } + [Test] + [NGitLabRetry] + public async Task Test_can_revert_commit() + { + // Arrange + using var context = await GitLabTestContext.CreateAsync(); + var project = context.CreateProject(); + + var testBranchName = "revert-test"; + + var repositoryClient = context.Client.GetRepository(project.Id); + repositoryClient.Branches.Create(new BranchCreate + { + Name = testBranchName, + Ref = project.DefaultBranch, + }); + + var commitClient = context.Client.GetCommits(project.Id); + var testCommit = commitClient.Create(new CommitCreate + { + Branch = testBranchName, + CommitMessage = "This commit will be reverted", + Actions = + [ + new() + { + Action = "update", + Content = "Testing commit revert", + FilePath = "README.md", + }, + ], + }); + + var compareResults = repositoryClient.Compare(new CompareQuery(project.DefaultBranch, testBranchName)); + Assert.That(compareResults.Diff, Has.Length.EqualTo(1)); + + // Act + var revertedCommit = commitClient.Revert(new CommitRevert + { + Branch = testBranchName, + Sha = testCommit.Id, + }); + + // Assert + compareResults = repositoryClient.Compare(new CompareQuery(project.DefaultBranch, testBranchName)); + Assert.That(compareResults.Diff, Is.Empty); + } + [TestCase(false)] [TestCase(true)] [NGitLabRetry] diff --git a/NGitLab.Tests/EventTests.cs b/NGitLab.Tests/EventTests.cs index 23eeb40d..cef7af6a 100644 --- a/NGitLab.Tests/EventTests.cs +++ b/NGitLab.Tests/EventTests.cs @@ -13,33 +13,72 @@ public class EventTests [NGitLabRetry] public async Task Test_get_user_events_works() { + // Arrange using var context = await GitLabTestContext.CreateAsync(); var project = context.CreateProject(); var currentUserId = context.Client.Users.Current.Id; - var userEvents = context.Client.GetUserEvents(currentUserId); - var globalEvents = context.Client.GetEvents(); + var userEventClient = context.Client.GetUserEvents(currentUserId); - var firstEvent = userEvents.Get(new EventQuery { After = DateTime.UtcNow.AddMonths(-1) }).FirstOrDefault(); + // Act + var firstEvent = userEventClient.Get(new EventQuery { After = DateTime.UtcNow.AddMonths(-1) }).FirstOrDefault(); - if (firstEvent != null) - { - Assert.That(firstEvent.AuthorId, Is.EqualTo(currentUserId)); - } + // Assert + Assert.That(firstEvent, Is.Not.Null); + Assert.That(firstEvent.AuthorId, Is.EqualTo(currentUserId)); } [Test] [NGitLabRetry] public async Task Test_get_global_events_works() { + // Arrange using var context = await GitLabTestContext.CreateAsync(); var project = context.CreateProject(); - var currentUserId = context.Client.Users.Current.Id; - var globalEvents = context.Client.GetEvents(); + var globalEventClient = context.Client.GetEvents(); - var firstEvent = globalEvents.Get(new EventQuery { After = DateTime.UtcNow.AddMonths(-1) }).FirstOrDefault(); + // Act + var firstEvent = globalEventClient.Get(new EventQuery { After = DateTime.UtcNow.AddMonths(-1) }).FirstOrDefault(); + // Assert Assert.That(firstEvent, Is.Not.Null); } + + [Test] + [NGitLabRetry] + public async Task Test_get_events_of_specific_action_type() + { + // Arrange + using var context = await GitLabTestContext.CreateAsync(); + var project = context.CreateProject(); + + var issueClient = context.Client.Issues; + var issueTitle = $"Temporary Issue {Guid.NewGuid()}"; + var issue = issueClient.Create(new IssueCreate + { + ProjectId = project.Id, + Title = issueTitle, + }); + + issueClient.Edit(new IssueEdit + { + ProjectId = project.Id, + IssueId = issue.IssueId, + State = "close", + }); + + var currentUserId = context.Client.Users.Current.Id; + var userEventClient = context.Client.GetUserEvents(currentUserId); + + // Act + var closedEvents = userEventClient.Get(new EventQuery { Action = EventAction.Closed }).ToArray(); + + // Assert + Assert.That(closedEvents.All(e => e.Action.EnumValue is EventAction.Closed), Is.True); + + var issueClosedEvent = closedEvents.SingleOrDefault(e => string.Equals(e.TargetTitle, issueTitle, StringComparison.Ordinal)); + Assert.That(issueClosedEvent, Is.Not.Null); + Assert.That(issueClosedEvent.TargetType.EnumValue, Is.EqualTo(EventTargetType.Issue)); + } } diff --git a/NGitLab.Tests/Impl/UtilsTests.cs b/NGitLab.Tests/Impl/UtilsTests.cs index f4df615f..b785b25e 100644 --- a/NGitLab.Tests/Impl/UtilsTests.cs +++ b/NGitLab.Tests/Impl/UtilsTests.cs @@ -6,7 +6,7 @@ namespace NGitLab.Tests.Impl; public class UtilsTests { [TestCase(EventAction.PushedTo, "pushed+to")] - [TestCase(EventAction.Accepted, "Accepted")] + [TestCase(EventAction.Accepted, "accepted")] public void AddParameter_ConsidersEnumMemberAttribute(EventAction value, string expectedQueryParamValue) { const string basePath = "https://gitlab.org/api/v4/stuff"; diff --git a/NGitLab/ICommitClient.cs b/NGitLab/ICommitClient.cs index 7f290df1..050777df 100644 --- a/NGitLab/ICommitClient.cs +++ b/NGitLab/ICommitClient.cs @@ -27,6 +27,11 @@ public interface ICommitClient /// Commit CherryPick(CommitCherryPick cherryPick); + /// + /// Reverts a specific branch commit + /// + Commit Revert(CommitRevert revert); + /// /// Get merge requests related to a commit /// diff --git a/NGitLab/Impl/CommitClient.cs b/NGitLab/Impl/CommitClient.cs index 1daf6a49..8f00cf59 100644 --- a/NGitLab/Impl/CommitClient.cs +++ b/NGitLab/Impl/CommitClient.cs @@ -55,4 +55,9 @@ public GitLabCollectionResponse GetRelatedMergeRequestsAsync(Relat { return _api.Get().GetAllAsync(_repoPath + $"/commits/{query.Sha}/merge_requests"); } + + public Commit Revert(CommitRevert revert) + { + return _api.Post().With(revert).To($"{_repoPath}/commits/{revert.Sha}/revert"); + } } diff --git a/NGitLab/Models/CommitRevert.cs b/NGitLab/Models/CommitRevert.cs new file mode 100644 index 00000000..dcc87c14 --- /dev/null +++ b/NGitLab/Models/CommitRevert.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace NGitLab.Models; + +public class CommitRevert +{ + [Required] + [JsonIgnore] + public Sha1 Sha { get; set; } + + [Required] + [JsonPropertyName("branch")] + public string Branch { get; set; } + + [JsonPropertyName("dry_run")] + public bool? DryRun { get; set; } +} diff --git a/NGitLab/Models/EventAction.cs b/NGitLab/Models/EventAction.cs index 9457761c..bd76e2f9 100644 --- a/NGitLab/Models/EventAction.cs +++ b/NGitLab/Models/EventAction.cs @@ -4,21 +4,37 @@ namespace NGitLab.Models; public enum EventAction { + [EnumMember(Value = "accepted")] Accepted, + [EnumMember(Value = "approved")] Approved, + [EnumMember(Value = "created")] Created, + [EnumMember(Value = "updated")] Updated, + [EnumMember(Value = "uploaded")] Uploaded, + [EnumMember(Value = "deleted")] Deleted, + [EnumMember(Value = "closed")] Closed, + [EnumMember(Value = "opened")] Opened, + [EnumMember(Value = "reopened")] Reopened, + [EnumMember(Value = "pushed")] Pushed, + [EnumMember(Value = "commented")] Commented, + [EnumMember(Value = "merged")] Merged, + [EnumMember(Value = "joined")] Joined, + [EnumMember(Value = "left")] Left, + [EnumMember(Value = "destroyed")] Destroyed, + [EnumMember(Value = "expired")] Expired, [EnumMember(Value = "pushed new")] PushedNew, diff --git a/NGitLab/Models/IssueCreate.cs b/NGitLab/Models/IssueCreate.cs index 92dc888e..212e7258 100644 --- a/NGitLab/Models/IssueCreate.cs +++ b/NGitLab/Models/IssueCreate.cs @@ -38,4 +38,7 @@ public class IssueCreate [JsonPropertyName("epic_id")] public long? EpicId { get; set; } + + [JsonPropertyName("weight")] + public int? Weight { get; set; } } diff --git a/NGitLab/Models/IssueEdit.cs b/NGitLab/Models/IssueEdit.cs index 3a1c4e43..0c69dfa4 100644 --- a/NGitLab/Models/IssueEdit.cs +++ b/NGitLab/Models/IssueEdit.cs @@ -41,4 +41,7 @@ public class IssueEdit [JsonPropertyName("epic_id")] public long? EpicId { get; set; } + + [JsonPropertyName("weight")] + public int? Weight { get; set; } } diff --git a/NGitLab/Models/ProjectUpdate.cs b/NGitLab/Models/ProjectUpdate.cs index e60dde17..2ede3ec4 100644 --- a/NGitLab/Models/ProjectUpdate.cs +++ b/NGitLab/Models/ProjectUpdate.cs @@ -22,8 +22,12 @@ public sealed class ProjectUpdate [Obsolete("Deprecated by GitLab. Use IssuesAccessLevel instead")] public bool? IssuesEnabled { get; set; } + [JsonIgnore] + [Obsolete("Use IssuesAccessLevel instead")] + public string IssuesAccessLeve { get => IssuesAccessLevel; set => IssuesAccessLevel = value; } + [JsonPropertyName("issues_access_level")] - public string IssuesAccessLeve { get; set; } + public string IssuesAccessLevel { get; set; } [JsonPropertyName("merge_pipelines_enabled")] public bool MergePipelinesEnabled { get; set; } @@ -92,6 +96,9 @@ public sealed class ProjectUpdate [JsonPropertyName("request_access_enabled")] public bool? RequestAccessEnabled { get; set; } + [JsonPropertyName("repository_access_level")] + public string RepositoryAccessLevel { get; set; } + [JsonPropertyName("packages_enabled")] public bool? PackagesEnabled { get; set; } diff --git a/NGitLab/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI.Unshipped.txt index f3e14d2b..c4c7ece5 100644 --- a/NGitLab/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI.Unshipped.txt @@ -132,6 +132,7 @@ NGitLab.ICommitClient.Create(NGitLab.Models.CommitCreate commit) -> NGitLab.Mode NGitLab.ICommitClient.GetCommit(string ref) -> NGitLab.Models.Commit NGitLab.ICommitClient.GetJobStatus(string branchName) -> NGitLab.JobStatus NGitLab.ICommitClient.GetRelatedMergeRequestsAsync(NGitLab.Models.RelatedMergeRequestsQuery query) -> NGitLab.GitLabCollectionResponse +NGitLab.ICommitClient.Revert(NGitLab.Models.CommitRevert revert) -> NGitLab.Models.Commit NGitLab.ICommitStatusClient NGitLab.ICommitStatusClient.AddOrUpdate(NGitLab.Models.CommitStatusCreate status) -> NGitLab.Models.CommitStatusCreate NGitLab.ICommitStatusClient.AllBySha(string commitSha) -> System.Collections.Generic.IEnumerable @@ -475,6 +476,7 @@ NGitLab.Impl.CommitClient.Create(NGitLab.Models.CommitCreate commit) -> NGitLab. NGitLab.Impl.CommitClient.GetCommit(string ref) -> NGitLab.Models.Commit NGitLab.Impl.CommitClient.GetJobStatus(string branchName) -> NGitLab.JobStatus NGitLab.Impl.CommitClient.GetRelatedMergeRequestsAsync(NGitLab.Models.RelatedMergeRequestsQuery query) -> NGitLab.GitLabCollectionResponse +NGitLab.Impl.CommitClient.Revert(NGitLab.Models.CommitRevert revert) -> NGitLab.Models.Commit NGitLab.Impl.CommitStatusClient NGitLab.Impl.CommitStatusClient.AddOrUpdate(NGitLab.Models.CommitStatusCreate status) -> NGitLab.Models.CommitStatusCreate NGitLab.Impl.CommitStatusClient.AllBySha(string commitSha) -> System.Collections.Generic.IEnumerable @@ -1530,6 +1532,14 @@ NGitLab.Models.CommitRefType NGitLab.Models.CommitRefType.All = 0 -> NGitLab.Models.CommitRefType NGitLab.Models.CommitRefType.Branch = 1 -> NGitLab.Models.CommitRefType NGitLab.Models.CommitRefType.Tag = 2 -> NGitLab.Models.CommitRefType +NGitLab.Models.CommitRevert +NGitLab.Models.CommitRevert.Branch.get -> string +NGitLab.Models.CommitRevert.Branch.set -> void +NGitLab.Models.CommitRevert.CommitRevert() -> void +NGitLab.Models.CommitRevert.DryRun.get -> bool? +NGitLab.Models.CommitRevert.DryRun.set -> void +NGitLab.Models.CommitRevert.Sha.get -> NGitLab.Sha1 +NGitLab.Models.CommitRevert.Sha.set -> void NGitLab.Models.CommitStats NGitLab.Models.CommitStats.Additions.get -> int NGitLab.Models.CommitStats.Additions.set -> void @@ -2291,6 +2301,8 @@ NGitLab.Models.IssueCreate.ProjectId.get -> long NGitLab.Models.IssueCreate.ProjectId.set -> void NGitLab.Models.IssueCreate.Title.get -> string NGitLab.Models.IssueCreate.Title.set -> void +NGitLab.Models.IssueCreate.Weight.get -> int? +NGitLab.Models.IssueCreate.Weight.set -> void NGitLab.Models.IssueEdit NGitLab.Models.IssueEdit.AssigneeId.get -> long? NGitLab.Models.IssueEdit.AssigneeId.set -> void @@ -2315,6 +2327,8 @@ NGitLab.Models.IssueEdit.State.get -> string NGitLab.Models.IssueEdit.State.set -> void NGitLab.Models.IssueEdit.Title.get -> string NGitLab.Models.IssueEdit.Title.set -> void +NGitLab.Models.IssueEdit.Weight.get -> int? +NGitLab.Models.IssueEdit.Weight.set -> void NGitLab.Models.IssueEpic NGitLab.Models.IssueEpic.EpicId.get -> long NGitLab.Models.IssueEpic.EpicId.set -> void @@ -3734,6 +3748,8 @@ NGitLab.Models.ProjectUpdate.GroupRunnersEnabled.get -> bool? NGitLab.Models.ProjectUpdate.GroupRunnersEnabled.set -> void NGitLab.Models.ProjectUpdate.IssuesAccessLeve.get -> string NGitLab.Models.ProjectUpdate.IssuesAccessLeve.set -> void +NGitLab.Models.ProjectUpdate.IssuesAccessLevel.get -> string +NGitLab.Models.ProjectUpdate.IssuesAccessLevel.set -> void NGitLab.Models.ProjectUpdate.IssuesEnabled.get -> bool? NGitLab.Models.ProjectUpdate.IssuesEnabled.set -> void NGitLab.Models.ProjectUpdate.JobsEnabled.get -> bool? @@ -3763,6 +3779,8 @@ NGitLab.Models.ProjectUpdate.PublicBuilds.get -> bool? NGitLab.Models.ProjectUpdate.PublicBuilds.set -> void NGitLab.Models.ProjectUpdate.RemoveSourceBranchAfterMerge.get -> bool? NGitLab.Models.ProjectUpdate.RemoveSourceBranchAfterMerge.set -> void +NGitLab.Models.ProjectUpdate.RepositoryAccessLevel.get -> string +NGitLab.Models.ProjectUpdate.RepositoryAccessLevel.set -> void NGitLab.Models.ProjectUpdate.RequestAccessEnabled.get -> bool? NGitLab.Models.ProjectUpdate.RequestAccessEnabled.set -> void NGitLab.Models.ProjectUpdate.ResolveOutdatedDiffDiscussions.get -> bool?