diff --git a/NGitLab.Mock.Tests/CommitsMockTests.cs b/NGitLab.Mock.Tests/CommitsMockTests.cs index 07b09bd4..b071754b 100644 --- a/NGitLab.Mock.Tests/CommitsMockTests.cs +++ b/NGitLab.Mock.Tests/CommitsMockTests.cs @@ -1,5 +1,6 @@ using System.Linq; using NGitLab.Mock.Config; +using NGitLab.Models; using NUnit.Framework; namespace NGitLab.Mock.Tests @@ -77,5 +78,28 @@ public void Test_two_branches_can_be_created_from_same_commit() Assert.IsNotEmpty(commitFromBranch2.Parents); Assert.AreEqual(commitFromBranch1.Parents[0], commitFromBranch2.Parents[0]); } + + [Test] + public void Test_commits_can_be_cherry_pick() + { + using var server = new GitLabConfig() + .WithUser("user1", isDefault: true) + .WithProject("test-project", id: 1, addDefaultUserAsMaintainer: true, configure: project => project + .WithCommit("Initial commit") + .WithCommit("Changes with tag", sourceBranch: "branch_1")) + .BuildServer(); + + var client = server.CreateClient(); + var repository = client.GetRepository(1); + var commitFromBranch1 = repository.GetCommits("branch_1").FirstOrDefault(); + Assert.NotNull(commitFromBranch1); + + var cherryPicked = client.GetCommits(1).CherryPick(new CommitCherryPick + { + Sha = commitFromBranch1.Id, + Branch = "main", + }); + Assert.NotNull(cherryPicked); + } } } diff --git a/NGitLab.Mock/Clients/CommitClient.cs b/NGitLab.Mock/Clients/CommitClient.cs index b93740b5..62d473a0 100644 --- a/NGitLab.Mock/Clients/CommitClient.cs +++ b/NGitLab.Mock/Clients/CommitClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using NGitLab.Mock.Internals; using NGitLab.Models; @@ -37,6 +37,17 @@ public Commit GetCommit(string @ref) } } + public Commit CherryPick(CommitCherryPick cherryPick) + { + using (Context.BeginOperationScope()) + { + var project = GetProject(_projectId, ProjectPermission.Contribute); + var gitCommit = project.Repository.CherryPick(cherryPick); + + return gitCommit.ToCommitClient(project); + } + } + public JobStatus GetJobStatus(string branchName) { throw new NotImplementedException(); diff --git a/NGitLab.Mock/PublicAPI.Unshipped.txt b/NGitLab.Mock/PublicAPI.Unshipped.txt index 8a6183ed..ee055a5e 100644 --- a/NGitLab.Mock/PublicAPI.Unshipped.txt +++ b/NGitLab.Mock/PublicAPI.Unshipped.txt @@ -941,6 +941,7 @@ NGitLab.Mock.ReleaseTag.ReleaseNotes.set -> void NGitLab.Mock.ReleaseTag.ReleaseTag(string name, string releaseNotes) -> void NGitLab.Mock.Repository NGitLab.Mock.Repository.Checkout(string committishOrBranchNameSpec) -> void +NGitLab.Mock.Repository.CherryPick(NGitLab.Models.CommitCherryPick commitCherryPick) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.Commit(NGitLab.Mock.User user, string message) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.Commit(NGitLab.Mock.User user, string message, string targetBranch, System.Collections.Generic.IEnumerable files) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.Commit(NGitLab.Mock.User user, string message, System.Collections.Generic.IEnumerable files) -> LibGit2Sharp.Commit diff --git a/NGitLab.Mock/Repository.cs b/NGitLab.Mock/Repository.cs index db0f415d..fb7183f9 100644 --- a/NGitLab.Mock/Repository.cs +++ b/NGitLab.Mock/Repository.cs @@ -207,6 +207,20 @@ public Commit Commit(CommitCreate commitCreate) return commit; } + public Commit CherryPick(CommitCherryPick commitCherryPick) + { + var repo = GetGitRepository(); + Commands.Checkout(repo, commitCherryPick.Branch); + + var commit = GetCommit(commitCherryPick.Sha.ToString()); + var options = new CherryPickOptions + { + CommitOnSuccess = commitCherryPick.Message?.Length > 0, + }; + var cherryPickResult = repo.CherryPick(commit, commit.Author, options); + return cherryPickResult.Commit ?? repo.Commit(commit.Message, commit.Author, commit.Committer); + } + public void Checkout(string committishOrBranchNameSpec) { Commands.Checkout(GetGitRepository(), committishOrBranchNameSpec); @@ -396,15 +410,7 @@ public IEnumerable GetCommits(string @ref) public Commit GetCommit(string reference) { var repository = GetGitRepository(); - var branchTip = GetBranchTipCommit(reference); - if (branchTip != null) - return branchTip; - - var tag = repository.Tags[reference]; - if (tag?.PeeledTarget is Commit commit) - return commit; - - return repository.Commits.SingleOrDefault(c => string.Equals(c.Sha, reference, StringComparison.Ordinal)); + return repository.Lookup(reference); } public Patch GetBranchFullPatch(string branchName) diff --git a/NGitLab.Tests/CommitsTests.cs b/NGitLab.Tests/CommitsTests.cs index 2876a0e8..356965b3 100644 --- a/NGitLab.Tests/CommitsTests.cs +++ b/NGitLab.Tests/CommitsTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using NGitLab.Models; using NGitLab.Tests.Docker; @@ -76,5 +77,41 @@ public async Task Test_can_get_merge_request_associated_to_commit() var mergeRequest = mergeRequests.Single(); Assert.AreEqual(mergeRequestTitle, mergeRequest.Title); } + + [Test] + [NGitLabRetry] + public async Task Test_can_cherry_pick_commit() + { + using var context = await GitLabTestContext.CreateAsync(); + var project = context.CreateProject(); + var repository = context.Client.GetRepository(project.Id); + var commitClient = context.Client.GetCommits(project.Id); + + repository.Branches.Create(new BranchCreate { Name = "test-cherry-pick", Ref = project.DefaultBranch }); + + var commit = commitClient.Create(new CommitCreate + { + Branch = "test-cherry-pick", + CommitMessage = "Test to cherry-pick", + Actions = new List + { + new() + { + Action = "update", + Content = "Test to cherry-pick", + FilePath = "README.md", + }, + }, + }); + + var cherryPickedCommit = commitClient.CherryPick(new CommitCherryPick + { + Branch = project.DefaultBranch, + Sha = commit.Id, + }); + + var latestCommit = commitClient.GetCommit(project.DefaultBranch); + Assert.AreEqual(cherryPickedCommit.Id, latestCommit.Id); + } } } diff --git a/NGitLab/ICommitClient.cs b/NGitLab/ICommitClient.cs index e9f296c8..cfcd36e9 100644 --- a/NGitLab/ICommitClient.cs +++ b/NGitLab/ICommitClient.cs @@ -22,6 +22,11 @@ public interface ICommitClient /// Commit GetCommit(string @ref); + /// + /// Cherry-picks a commit to a given branch. + /// + Commit CherryPick(CommitCherryPick cherryPick); + /// /// Get merge requests related to a commit /// diff --git a/NGitLab/Impl/CommitClient.cs b/NGitLab/Impl/CommitClient.cs index cb61aab0..f6ece503 100644 --- a/NGitLab/Impl/CommitClient.cs +++ b/NGitLab/Impl/CommitClient.cs @@ -23,6 +23,11 @@ public Commit GetCommit(string @ref) return _api.Get().To(_repoPath + $"/commits/{@ref}"); } + public Commit CherryPick(CommitCherryPick cherryPick) + { + return _api.Post().With(cherryPick).To($"{_repoPath}/commits/{cherryPick.Sha}/cherry_pick"); + } + public JobStatus GetJobStatus(string branchName) { var encodedBranch = WebUtility.UrlEncode(branchName); diff --git a/NGitLab/Models/CommitCherryPick.cs b/NGitLab/Models/CommitCherryPick.cs new file mode 100644 index 00000000..6ef0f6ca --- /dev/null +++ b/NGitLab/Models/CommitCherryPick.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace NGitLab.Models +{ + public class CommitCherryPick + { + [Required] + [JsonIgnore] + public Sha1 Sha { get; set; } + + [Required] + [JsonPropertyName("branch")] + public string Branch { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + } +} diff --git a/NGitLab/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI.Unshipped.txt index a6774b99..ca8f18ee 100644 --- a/NGitLab/PublicAPI.Unshipped.txt +++ b/NGitLab/PublicAPI.Unshipped.txt @@ -110,6 +110,7 @@ NGitLab.IBranchClient.Unprotect(string name) -> NGitLab.Models.Branch NGitLab.IClusterClient NGitLab.IClusterClient.All.get -> System.Collections.Generic.IEnumerable NGitLab.ICommitClient +NGitLab.ICommitClient.CherryPick(NGitLab.Models.CommitCherryPick cherryPick) -> NGitLab.Models.Commit NGitLab.ICommitClient.Create(NGitLab.Models.CommitCreate commit) -> NGitLab.Models.Commit NGitLab.ICommitClient.GetCommit(string ref) -> NGitLab.Models.Commit NGitLab.ICommitClient.GetJobStatus(string branchName) -> NGitLab.JobStatus @@ -389,6 +390,7 @@ NGitLab.Impl.ClusterClient NGitLab.Impl.ClusterClient.All.get -> System.Collections.Generic.IEnumerable NGitLab.Impl.ClusterClient.ClusterClient(NGitLab.Impl.API api, int projectId) -> void NGitLab.Impl.CommitClient +NGitLab.Impl.CommitClient.CherryPick(NGitLab.Models.CommitCherryPick cherryPick) -> NGitLab.Models.Commit NGitLab.Impl.CommitClient.CommitClient(NGitLab.Impl.API api, int projectId) -> void NGitLab.Impl.CommitClient.Create(NGitLab.Models.CommitCreate commit) -> NGitLab.Models.Commit NGitLab.Impl.CommitClient.GetCommit(string ref) -> NGitLab.Models.Commit @@ -1226,6 +1228,14 @@ NGitLab.Models.Commit.Stats -> NGitLab.Models.CommitStats NGitLab.Models.Commit.Status -> string NGitLab.Models.Commit.Title -> string NGitLab.Models.Commit.WebUrl -> string +NGitLab.Models.CommitCherryPick +NGitLab.Models.CommitCherryPick.Branch.get -> string +NGitLab.Models.CommitCherryPick.Branch.set -> void +NGitLab.Models.CommitCherryPick.CommitCherryPick() -> void +NGitLab.Models.CommitCherryPick.Message.get -> string +NGitLab.Models.CommitCherryPick.Message.set -> void +NGitLab.Models.CommitCherryPick.Sha.get -> NGitLab.Sha1 +NGitLab.Models.CommitCherryPick.Sha.set -> void NGitLab.Models.CommitCreate NGitLab.Models.CommitCreate.Actions -> System.Collections.Generic.IList NGitLab.Models.CommitCreate.AuthorEmail -> string