diff --git a/NGitLab.Mock.Tests/ProjectsMockTests.cs b/NGitLab.Mock.Tests/ProjectsMockTests.cs index ff865f98..062892d8 100644 --- a/NGitLab.Mock.Tests/ProjectsMockTests.cs +++ b/NGitLab.Mock.Tests/ProjectsMockTests.cs @@ -37,6 +37,49 @@ public void Test_project_can_be_cloned_by_default() Assert.IsTrue(Directory.Exists(tempDir.GetFullPath(".git"))); } + [Test] + public void Test_project_with_submodules() + { + using var tempDir = TemporaryDirectory.Create(); + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("ModuleA", configure: x => x.WithCommit(configure: c => c.WithFile("A.txt"))) + .WithProject("ModuleB", configure: x => x.WithCommit(configure: c => c.WithFile("B.txt"))) + .WithProject("Test", clonePath: tempDir.FullPath, configure: x => + x.WithCommit("Init", configure: c + => c.WithSubModule("ModuleA") + .WithSubModule("ModuleB"))) + .BuildServer(); + + Assert.IsTrue(Directory.Exists(tempDir.GetFullPath(".git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleA/.git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleA/A.txt"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/.git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/B.txt"))); + } + + [Test] + public void Test_project_with_nested_submodules() + { + using var tempDir = TemporaryDirectory.Create(); + using var server = new GitLabConfig() + .WithUser("Test", isDefault: true) + .WithProject("ModuleA", configure: x => x.WithCommit(configure: c => c.WithFile("A.txt"))) + .WithProject("ModuleB", configure: x => x.WithCommit(configure: c + => c.WithFile("B.txt") + .WithSubModule("ModuleA"))) + .WithProject("Test", clonePath: tempDir.FullPath, configure: x => + x.WithCommit(configure: c + => c.WithSubModule("ModuleB"))) + .BuildServer(); + + Assert.IsTrue(Directory.Exists(tempDir.GetFullPath(".git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/.git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/B.txt"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/ModuleA/.git"))); + Assert.IsTrue(System.IO.File.Exists(tempDir.GetFullPath("ModuleB/ModuleA/A.txt"))); + } + [Test] public void Test_projects_created_url_ends_with_namespace_and_name() { diff --git a/NGitLab.Mock/Config/GitLabCommit.cs b/NGitLab.Mock/Config/GitLabCommit.cs index 585e879e..7034142e 100644 --- a/NGitLab.Mock/Config/GitLabCommit.cs +++ b/NGitLab.Mock/Config/GitLabCommit.cs @@ -24,6 +24,11 @@ public class GitLabCommit : GitLabObject /// public IList Files { get; } = new List(); + /// + /// Submodules added at this commit + /// + public IList SubModules { get; } = new List(); + public IList Tags { get; } = new List(); /// diff --git a/NGitLab.Mock/Config/GitLabHelpers.cs b/NGitLab.Mock/Config/GitLabHelpers.cs index f73a8833..3ae2e4c8 100644 --- a/NGitLab.Mock/Config/GitLabHelpers.cs +++ b/NGitLab.Mock/Config/GitLabHelpers.cs @@ -448,6 +448,20 @@ public static GitLabCommit WithFile(this GitLabCommit commit, string relativePat }); } + /// + /// Add a submodule in commit + /// + public static GitLabCommit WithSubModule(this GitLabCommit commit, string projectName) + { + return Configure(commit, _ => + { + commit.SubModules.Add(new GitLabSubModuleDescriptor + { + ProjectName = projectName, + }); + }); + } + /// /// Add an issue description in project /// @@ -1234,7 +1248,7 @@ private static void CreateProject(GitLabServer server, GitLabProject project) StartInfo = new ProcessStartInfo { FileName = "git", - Arguments = $"clone {project.CloneParameters} \"{prj.SshUrl}\" \"{Path.GetFileName(project.ClonePath)}\"", + Arguments = $"-c protocol.file.allow=always clone {project.CloneParameters} \"{prj.SshUrl}\" \"{Path.GetFileName(project.ClonePath)}\" --recursive", RedirectStandardError = true, UseShellExecute = false, WorkingDirectory = folderPath, @@ -1283,9 +1297,12 @@ private static Commit CreateCommit(GitLabServer server, Project prj, GitLabCommi prj.Repository.CreateBranch(commit.SourceBranch); var files = commit.Files.Count == 0 - ? new[] { File.CreateFromText("test.txt", Guid.NewGuid().ToString()) } - : commit.Files.Select(x => File.CreateFromText(x.Path, x.Content ?? string.Empty)); - cmt = prj.Repository.Commit(user, commit.Message ?? Guid.NewGuid().ToString("D"), commit.SourceBranch, files); + ? new List() { File.CreateFromText("test.txt", Guid.NewGuid().ToString()) } + : commit.Files.Select(x => File.CreateFromText(x.Path, x.Content ?? string.Empty)).ToList(); + + var submodules = CreateSubModules(server, prj, commit); + + cmt = prj.Repository.Commit(user, commit.Message ?? Guid.NewGuid().ToString("D"), commit.SourceBranch, files, submodules); } else { @@ -1302,6 +1319,44 @@ private static Commit CreateCommit(GitLabServer server, Project prj, GitLabCommi return cmt; } + private static IEnumerable CreateSubModules(GitLabServer server, Project prj, GitLabCommit commit) + { + List submodules = new(); + foreach (var submodule in commit.SubModules) + { + var subModuleProject = server.AllProjects.FirstOrDefault(x => + x.Name.Equals(submodule.ProjectName, StringComparison.OrdinalIgnoreCase)); + if (subModuleProject is null) + { + throw new GitLabException($"Project {submodule.ProjectName} can't be found."); + } + + if (!subModuleProject.Repository.GetCommits().Any()) + { + throw new GitLabException("Project added as a module must have least one commit."); + } + + using var process = Process.Start( + new ProcessStartInfo("git", $"-c protocol.file.allow=always submodule add \"{subModuleProject.SshUrl}\" \"{subModuleProject.Name}\"") + { + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = prj.Repository.FullPath, + }); + + process.WaitForExit(); + if (process.ExitCode != 0) + { + var error = process.StandardError.ReadToEnd(); + throw new GitLabException($"Cannot add submodule: {error}"); + } + + submodules.Add(subModuleProject.Name); + } + + return submodules; + } + private static void CreateLabel(Group group, GitLabLabel label) { group.Labels.Add(new Label diff --git a/NGitLab.Mock/Config/GitLabSubModuleDescriptor.cs b/NGitLab.Mock/Config/GitLabSubModuleDescriptor.cs new file mode 100644 index 00000000..588a0984 --- /dev/null +++ b/NGitLab.Mock/Config/GitLabSubModuleDescriptor.cs @@ -0,0 +1,13 @@ +namespace NGitLab.Mock.Config +{ + /// + /// Describe a sub module in project repository + /// + public class GitLabSubModuleDescriptor + { + /// + /// Project's ID added as a submodule + /// + public string ProjectName { get; init; } + } +} diff --git a/NGitLab.Mock/PublicAPI.Unshipped.txt b/NGitLab.Mock/PublicAPI.Unshipped.txt index 796103c3..2aa6ffc9 100644 --- a/NGitLab.Mock/PublicAPI.Unshipped.txt +++ b/NGitLab.Mock/PublicAPI.Unshipped.txt @@ -124,6 +124,7 @@ NGitLab.Mock.Config.GitLabCommit.DeleteSourceBranch.get -> bool NGitLab.Mock.Config.GitLabCommit.DeleteSourceBranch.set -> void NGitLab.Mock.Config.GitLabCommit.FromBranch.get -> string NGitLab.Mock.Config.GitLabCommit.FromBranch.set -> void +NGitLab.Mock.Config.GitLabCommit.SubModules.get -> System.Collections.Generic.IList NGitLab.Mock.Config.GitLabConfig.DefaultBranch.get -> string NGitLab.Mock.Config.GitLabConfig.DefaultBranch.set -> void NGitLab.Mock.Config.GitLabConfig.DefaultUser.get -> string @@ -210,6 +211,10 @@ NGitLab.Mock.Config.GitLabReleaseInfo.ReleasedAt.set -> void NGitLab.Mock.Config.GitLabReleaseInfo.TagName.get -> string NGitLab.Mock.Config.GitLabReleaseInfo.TagName.set -> void NGitLab.Mock.Config.GitLabReleaseInfoCollection +NGitLab.Mock.Config.GitLabSubModuleDescriptor +NGitLab.Mock.Config.GitLabSubModuleDescriptor.GitLabSubModuleDescriptor() -> void +NGitLab.Mock.Config.GitLabSubModuleDescriptor.ProjectName.get -> string +NGitLab.Mock.Config.GitLabSubModuleDescriptor.ProjectName.init -> void NGitLab.Mock.EffectivePermissions NGitLab.Mock.EffectivePermissions.GetAccessLevel(NGitLab.Mock.User user) -> NGitLab.Models.AccessLevel? NGitLab.Mock.EffectivePermissions.GetEffectivePermission(NGitLab.Mock.User user) -> NGitLab.Mock.EffectiveUserPermission @@ -956,6 +961,7 @@ 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, string targetBranch, System.Collections.Generic.IEnumerable files, System.Collections.Generic.IEnumerable submodules) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.Commit(NGitLab.Mock.User user, string message, System.Collections.Generic.IEnumerable files) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.Commit(NGitLab.Models.CommitCreate commitCreate) -> LibGit2Sharp.Commit NGitLab.Mock.Repository.CreateAndCheckoutBranch(string branchName) -> LibGit2Sharp.Branch @@ -1230,6 +1236,7 @@ static NGitLab.Mock.Config.GitLabHelpers.WithMilestone(this NGitLab.Mock.Config. static NGitLab.Mock.Config.GitLabHelpers.WithPipeline(this NGitLab.Mock.Config.GitLabProject project, string ref, System.Action configure) -> NGitLab.Mock.Config.GitLabProject static NGitLab.Mock.Config.GitLabHelpers.WithProject(this NGitLab.Mock.Config.GitLabConfig config, string name = null, int id = 0, string namespace = null, string description = null, string defaultBranch = null, NGitLab.Models.VisibilityLevel visibility = NGitLab.Models.VisibilityLevel.Internal, bool initialCommit = false, bool addDefaultUserAsMaintainer = false, string clonePath = null, string cloneParameters = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabConfig static NGitLab.Mock.Config.GitLabHelpers.WithRelease(this NGitLab.Mock.Config.GitLabProject project, string author, string tagName, System.DateTime? createdAt = null, System.DateTime? releasedAt = null) -> NGitLab.Mock.Config.GitLabProject +static NGitLab.Mock.Config.GitLabHelpers.WithSubModule(this NGitLab.Mock.Config.GitLabCommit commit, string projectName) -> NGitLab.Mock.Config.GitLabCommit static NGitLab.Mock.Config.GitLabHelpers.WithSystemComment(this NGitLab.Mock.Config.GitLabIssue issue, string message = null, string innerHtml = null, int id = 0, string author = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null) -> NGitLab.Mock.Config.GitLabIssue static NGitLab.Mock.Config.GitLabHelpers.WithSystemComment(this NGitLab.Mock.Config.GitLabMergeRequest mergeRequest, string message = null, string innerHtml = null, int id = 0, string author = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null) -> NGitLab.Mock.Config.GitLabMergeRequest static NGitLab.Mock.Config.GitLabHelpers.WithUser(this NGitLab.Mock.Config.GitLabConfig config, string username, string name = null, string email = null, string avatarUrl = null, bool isAdmin = false, bool isDefault = false, System.Action configure = null) -> NGitLab.Mock.Config.GitLabConfig diff --git a/NGitLab.Mock/Repository.cs b/NGitLab.Mock/Repository.cs index 9808a9c2..a6c9372c 100644 --- a/NGitLab.Mock/Repository.cs +++ b/NGitLab.Mock/Repository.cs @@ -157,7 +157,7 @@ public IReadOnlyCollection GetAllBranches() public Commit Commit(User user, string message) { - return Commit(user, message, targetBranch: null, new[] { File.CreateFromText("test.txt", Guid.NewGuid().ToString()) }); + return Commit(user, message, targetBranch: null, new[] { File.CreateFromText("test.txt", Guid.NewGuid().ToString()) }, Enumerable.Empty()); } public Commit Commit(User user, string message, IEnumerable files) @@ -166,6 +166,11 @@ public Commit Commit(User user, string message, IEnumerable files) } public Commit Commit(User user, string message, string targetBranch, IEnumerable files) + { + return Commit(user, message, targetBranch, files, Enumerable.Empty()); + } + + public Commit Commit(User user, string message, string targetBranch, IEnumerable files, IEnumerable submodules) { var repository = GetGitRepository(); if (targetBranch != null) @@ -181,6 +186,11 @@ public Commit Commit(User user, string message, string targetBranch, IEnumerable repository.Index.Add(file.Path); } + foreach (var submodule in submodules) + { + repository.Index.Add(submodule); + } + repository.Index.Write(); var author = new Signature(user.UserName, user.Email, DateTimeOffset.UtcNow);