diff --git a/.editorconfig b/.editorconfig
index af10f470..dc8e66ff 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -73,10 +73,10 @@ csharp_style_var_for_built_in_types = true : suggestion
csharp_style_var_when_type_is_apparent = true : warning
# Expression-Bodied members
-csharp_style_expression_bodied_accessors = true : suggestion
-csharp_style_expression_bodied_indexers = true : suggestion
-csharp_style_expression_bodied_operators = true : suggestion
-csharp_style_expression_bodied_properties = true : suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
# Explicitly disabled due to difference in coding style between source and tests
#csharp_style_expression_bodied_constructors = true : warning
#csharp_style_expression_bodied_methods = true : warning
@@ -101,7 +101,7 @@ csharp_style_conditional_delegate_call = true : warning
csharp_style_throw_expression = true : warning
# Code block preferences
-csharp_prefer_braces = when_multiline : suggestion
+csharp_prefer_braces = when_multiline:suggestion
## Formatting conventions
# Dotnet formatting settings:
@@ -141,6 +141,16 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping options
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+dotnet_diagnostic.SA1507.severity = error
## Naming conventions
[*.{cs,vb}]
@@ -159,7 +169,7 @@ dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
-dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
+dotnet_naming_rule.constants_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
@@ -198,7 +208,7 @@ dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
-dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
+dotnet_naming_rule.local_functions_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
@@ -216,7 +226,7 @@ dotnet_naming_symbols.type_parameter_symbol.applicable_accessibilities = *
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
-dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_rule.members_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.all_members.applicable_kinds = *
@@ -373,3 +383,6 @@ dotnet_diagnostic.SA1636.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+end_of_line = crlf
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0c3b1a31..621af2e4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
create_nuget:
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0 # Get all the history so MinGit can compute the version
- name: Setup .NET Core (latest)
@@ -51,12 +51,8 @@ jobs:
# Keep in sync with the version in GitLabDockerContainer.cs
# Available tags: https://hub.docker.com/r/gitlab/gitlab-ee/tags
gitlab: [
- 'gitlab/gitlab-ee:15.0.5-ee.0',
- 'gitlab/gitlab-ee:15.1.6-ee.0',
'gitlab/gitlab-ee:15.4.6-ee.0',
- 'gitlab/gitlab-ee:15.6.8-ee.0',
- # Several MR-related tests fail against the following version. We need to investigate...
- # 'gitlab/gitlab-ee:15.10.0-ee.0',
+ 'gitlab/gitlab-ee:15.11.9-ee.0',
]
configuration: [ Release ]
fail-fast: false
@@ -69,7 +65,7 @@ jobs:
GITLAB_OMNIBUS_CONFIG: "external_url 'http://localhost:48624/'"
GITLAB_ROOT_PASSWORD: "Pa$$w0rd"
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Setup .NET Core (latest)
uses: actions/setup-dotnet@v3
- run: |
diff --git a/Directory.Build.props b/Directory.Build.props
index 82459655..1861a9b9 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
$(Company), NGitLab contributors
- 9.0
+ 10.0
true
strict
true
@@ -16,7 +16,7 @@
true
- LICENSE
+ MIT
README.md
@@ -37,17 +37,17 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/NGitLab.Mock.Tests/BotUserTests.cs b/NGitLab.Mock.Tests/BotUserTests.cs
new file mode 100644
index 00000000..97489197
--- /dev/null
+++ b/NGitLab.Mock.Tests/BotUserTests.cs
@@ -0,0 +1,41 @@
+using NGitLab.Models;
+using NUnit.Framework;
+
+namespace NGitLab.Mock.Tests
+{
+ public sealed class BotUserTests
+ {
+ [Test]
+ public void Test_project_bot_user()
+ {
+ using var server = new GitLabServer();
+ var group = new Group("test");
+ var project = new Project("test-project");
+ server.Groups.Add(group);
+ group.Projects.Add(project);
+
+ var bot = project.CreateBotUser("token_name", AccessLevel.Maintainer);
+
+ Assert.That(bot.Bot, Is.True);
+ Assert.That(bot.Name, Is.EqualTo("token_name"));
+ var permissions = project.GetEffectivePermissions();
+ var botPermission = permissions.GetEffectivePermission(bot);
+ Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer));
+ }
+
+ [Test]
+ public void Test_group_bot_user()
+ {
+ using var server = new GitLabServer();
+ var group = new Group("test");
+ server.Groups.Add(group);
+
+ var bot = group.CreateBotUser(AccessLevel.Maintainer);
+
+ Assert.That(bot.Bot, Is.True);
+ var permissions = group.GetEffectivePermissions();
+ var botPermission = permissions.GetEffectivePermission(bot);
+ Assert.That(botPermission.AccessLevel, Is.EqualTo(AccessLevel.Maintainer));
+ }
+ }
+}
diff --git a/NGitLab.Mock.Tests/MembersMockTests.cs b/NGitLab.Mock.Tests/MembersMockTests.cs
new file mode 100644
index 00000000..f973c2e0
--- /dev/null
+++ b/NGitLab.Mock.Tests/MembersMockTests.cs
@@ -0,0 +1,86 @@
+using System.Linq;
+using NGitLab.Mock.Config;
+using NUnit.Framework;
+
+namespace NGitLab.Mock.Tests
+{
+ public class MembersMockTests
+ {
+ [Test]
+ public void Test_members_group_all_direct([Values] bool isDefault)
+ {
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithUser("user2")
+ .WithGroup("G1", 1, addDefaultUserAsMaintainer: true)
+ .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer))
+ .BuildServer();
+
+ var client = server.CreateClient("user1");
+ var members = isDefault
+ ? client.Members.OfGroup("2")
+ : client.Members.OfGroup("2", includeInheritedMembers: false);
+
+ Assert.AreEqual(1, members.Count(), "Membership found are invalid");
+ }
+
+ [Test]
+ public void Test_members_group_all_inherited()
+ {
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithUser("user2")
+ .WithProject("Test")
+ .WithGroup("G1", 1, configure: g => g.WithUserPermission("user1", Models.AccessLevel.Maintainer))
+ .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer))
+ .BuildServer();
+
+ var client = server.CreateClient("user1");
+ var members = client.Members.OfGroup("2", includeInheritedMembers: true);
+
+ Assert.AreEqual(2, members.Count(), "Membership found are invalid");
+ }
+
+ [Test]
+ public void Test_members_project_all_direct([Values] bool isDefault)
+ {
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithUser("user2")
+ .WithUser("user3")
+ .WithGroup("G1", 1, addDefaultUserAsMaintainer: true)
+ .WithGroup("G2", 2, @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer))
+ .WithProject("Project", @namespace: "G1", configure: g =>
+ g.WithUserPermission("user3", Models.AccessLevel.Maintainer)
+ .WithGroupPermission("G2", Models.AccessLevel.Developer))
+ .BuildServer();
+
+ var client = server.CreateClient("user1");
+ var members = isDefault
+ ? client.Members.OfProject("1")
+ : client.Members.OfProject("1", includeInheritedMembers: false);
+
+ Assert.AreEqual(1, members.Count(), "Membership found are invalid");
+ }
+
+ [Test]
+ public void Test_members_project_all_inherited()
+ {
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithUser("user2")
+ .WithUser("user3")
+ .WithGroup("G1", addDefaultUserAsMaintainer: true)
+ .WithGroup("G2", @namespace: "G1", configure: g => g.WithUserPermission("user2", Models.AccessLevel.Maintainer))
+ .WithProject("Project", 1, @namespace: "G1", configure: g =>
+ g.WithUserPermission("user3", Models.AccessLevel.Maintainer)
+ .WithGroupPermission("G1/G2", Models.AccessLevel.Developer))
+ .BuildServer();
+
+ var client = server.CreateClient("user1");
+ var members = client.Members.OfProject("1", includeInheritedMembers: true);
+
+ Assert.AreEqual(3, members.Count(), "Membership found are invalid");
+ }
+ }
+}
diff --git a/NGitLab.Mock.Tests/MilestonesMockTests.cs b/NGitLab.Mock.Tests/MilestonesMockTests.cs
index 4339d90f..074dd2a7 100644
--- a/NGitLab.Mock.Tests/MilestonesMockTests.cs
+++ b/NGitLab.Mock.Tests/MilestonesMockTests.cs
@@ -99,5 +99,47 @@ public void Test_milestones_can_be_closed_and_activated_from_project()
Assert.AreEqual(1, activeMilestones.Length, "Active milestones count is invalid");
Assert.AreEqual(0, closedMilestones.Length, "Closed milestones count is invalid");
}
+
+ [Test]
+ public void Test_projects_merge_request_can_be_found_from_milestone()
+ {
+ const int ProjectId = 1;
+ const int MilestoneId = 1;
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithProject("Test", id: ProjectId, addDefaultUserAsMaintainer: true, configure: project => project
+ .WithMilestone("Milestone 1", id: MilestoneId)
+ .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1")
+ .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 1")
+ .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 2"))
+ .BuildServer();
+
+ var client = server.CreateClient();
+ var mergeRequests = client.GetMilestone(ProjectId).GetMergeRequests(MilestoneId).ToArray();
+ Assert.AreEqual(2, mergeRequests.Length, "Merge requests count is invalid");
+ }
+
+ [Test]
+ public void Test_groups_merge_request_can_be_found_from_milestone()
+ {
+ const int projectId = 1;
+ const int milestoneId = 1;
+ using var server = new GitLabConfig()
+ .WithUser("user1", isDefault: true)
+ .WithGroup("parentGroup", id: projectId, configure: group => group
+ .WithMilestone("Milestone 1", id: milestoneId))
+ .WithGroup("subGroup1", 2, @namespace: "parentGroup")
+ .WithGroup("subGroup2", 3, @namespace: "parentGroup")
+ .WithProject("project1", @namespace: "parentGroup/subGroup1", addDefaultUserAsMaintainer: true, configure: project => project
+ .WithMergeRequest("branch-01", title: "Merge request 1", milestone: "Milestone 1")
+ .WithMergeRequest("branch-02", title: "Merge request 2", milestone: "Milestone 2"))
+ .WithProject("project2", @namespace: "parentGroup/subGroup2", addDefaultUserAsMaintainer: true, configure: project => project
+ .WithMergeRequest("branch-03", title: "Merge request 3", milestone: "Milestone 1"))
+ .BuildServer();
+
+ var client = server.CreateClient();
+ var mergeRequests = client.GetGroupMilestone(projectId).GetMergeRequests(milestoneId).ToArray();
+ Assert.AreEqual(2, mergeRequests.Length, "Merge requests count is invalid");
+ }
}
}
diff --git a/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj b/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj
index a9180fe5..cb56d584 100644
--- a/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj
+++ b/NGitLab.Mock.Tests/NGitLab.Mock.Tests.csproj
@@ -7,9 +7,9 @@
-
+
-
+
diff --git a/NGitLab.Mock/Clients/IssueClient.cs b/NGitLab.Mock/Clients/IssueClient.cs
index d38712b2..bc1a342b 100644
--- a/NGitLab.Mock/Clients/IssueClient.cs
+++ b/NGitLab.Mock/Clients/IssueClient.cs
@@ -302,6 +302,16 @@ public Models.Issue Get(int projectId, int issueId)
return GetById(issueId);
}
+ public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId)
+ {
+ throw new NotImplementedException();
+ }
+
public Models.Issue GetById(int issueId)
{
using (Context.BeginOperationScope())
diff --git a/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs b/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs
index d58a3845..6b86a8c8 100644
--- a/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs
+++ b/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using NGitLab.Models;
@@ -62,7 +63,25 @@ public static Models.CommitInfo ToCommitInfo(this LibGit2Sharp.Commit commit)
internal static LibGit2Sharp.Commit GetLastCommitForFileChanges(this LibGit2Sharp.Repository repository, string filePath)
{
- return repository.Commits.QueryBy(filePath).FirstOrDefault()?.Commit;
+ try
+ {
+ return repository.Commits.QueryBy(filePath).FirstOrDefault()?.Commit;
+ }
+ catch (KeyNotFoundException)
+ {
+ // LibGit2Sharp sometimes fails with the following exception
+ // System.Collections.Generic.KeyNotFoundException: The given key '1d08df45e551942eaa70d9f5ab6f5f7665a3f5b3' was not present in the dictionary.
+ // at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
+ // at LibGit2Sharp.Core.FileHistory.FullHistory(IRepository repo, String path, CommitFilter filter)+MoveNext() in /_/LibGit2Sharp/Core/FileHistory.cs:line 120
+ // at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
+ // at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
+ // at NGitLab.Mock.Clients.LibGit2SharpExtensions.GetLastCommitForFileChanges(Repository repository, String filePath) in /_/NGitLab.Mock/Clients/LibGit2SharpExtensions.cs:line 65
+ // at NGitLab.Mock.Repository.GetFile(String filePath, String ref) in /_/NGitLab.Mock/Repository.cs:line 485
+ // at NGitLab.Mock.Clients.FileClient.Get(String filePath, String ref) in /_/NGitLab.Mock/Clients/FileClient.cs:line 77
+ // at NGitLab.Mock.Clients.FileClient.GetAsync(String filePath, String ref, CancellationToken cancellationToken) in /_/NGitLab.Mock/Clients/FileClient.cs:line 125
+ }
+
+ return null;
}
}
}
diff --git a/NGitLab.Mock/Clients/MembersClient.cs b/NGitLab.Mock/Clients/MembersClient.cs
index a516dfe2..7b33a3e2 100644
--- a/NGitLab.Mock/Clients/MembersClient.cs
+++ b/NGitLab.Mock/Clients/MembersClient.cs
@@ -98,7 +98,7 @@ public IEnumerable OfGroup(string groupId, bool includeInheritedMemb
using (Context.BeginOperationScope())
{
var group = GetGroup(groupId, GroupPermission.View);
- var members = group.GetEffectivePermissions().Permissions;
+ var members = group.GetEffectivePermissions(includeInheritedMembers).Permissions;
return members.Select(member => member.ToMembershipClient());
}
}
@@ -118,7 +118,7 @@ public IEnumerable OfProject(string projectId, bool includeInherited
using (Context.BeginOperationScope())
{
var project = GetProject(projectId, ProjectPermission.View);
- var members = project.GetEffectivePermissions().Permissions;
+ var members = project.GetEffectivePermissions(includeInheritedMembers).Permissions;
return members.Select(member => member.ToMembershipClient());
}
}
diff --git a/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs b/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs
index a10e304c..82ad734f 100644
--- a/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs
+++ b/NGitLab.Mock/Clients/MergeRequestDiscussionClient.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using NGitLab.Models;
namespace NGitLab.Mock.Clients
@@ -30,6 +32,21 @@ public IEnumerable All
}
}
+ public MergeRequestDiscussion Get(string id)
+ {
+ using (Context.BeginOperationScope())
+ {
+ var discussions = GetMergeRequest().GetDiscussions();
+ var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, id, StringComparison.Ordinal));
+ return discussion ?? throw new GitLabNotFoundException();
+ }
+ }
+
+ public Task GetAsync(string id, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(Get(id));
+ }
+
public MergeRequestDiscussion Add(Models.MergeRequestComment comment)
{
return Add(new MergeRequestDiscussionCreate
@@ -71,7 +88,7 @@ public MergeRequestDiscussion Resolve(MergeRequestDiscussionResolve resolve)
using (Context.BeginOperationScope())
{
var discussions = GetMergeRequest().GetDiscussions();
- var discussion = discussions.First(x => string.Equals(x.Id, resolve.Id, StringComparison.Ordinal));
+ var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, resolve.Id, StringComparison.Ordinal));
if (discussion == null)
throw new GitLabNotFoundException();
@@ -89,7 +106,7 @@ public void Delete(string discussionId, long noteId)
using (Context.BeginOperationScope())
{
var discussions = GetMergeRequest().GetDiscussions();
- var discussion = discussions.First(x => string.Equals(x.Id, discussionId, StringComparison.Ordinal));
+ var discussion = discussions.FirstOrDefault(x => string.Equals(x.Id, discussionId, StringComparison.Ordinal));
if (discussion == null)
throw new GitLabNotFoundException();
diff --git a/NGitLab.Mock/Clients/MilestoneClient.cs b/NGitLab.Mock/Clients/MilestoneClient.cs
index 5a265a20..0e688f6d 100644
--- a/NGitLab.Mock/Clients/MilestoneClient.cs
+++ b/NGitLab.Mock/Clients/MilestoneClient.cs
@@ -11,13 +11,12 @@ namespace NGitLab.Mock
internal sealed class MilestoneClient : ClientBase, IMilestoneClient
{
private readonly int _resourceId;
- private readonly MilestoneScope _scope;
public MilestoneClient(ClientContext context, int id, MilestoneScope scope)
: base(context)
{
_resourceId = id;
- _scope = scope;
+ Scope = scope;
}
public Models.Milestone this[int id]
@@ -26,35 +25,46 @@ public Models.Milestone this[int id]
{
using (Context.BeginOperationScope())
{
- var project = GetProject(_resourceId, ProjectPermission.View);
- return FindMilestone(id, project)?.ToClientMilestone();
+ return GetMilestone(id, false).ToClientMilestone();
}
}
}
public IEnumerable All => Get(new MilestoneQuery());
- public MilestoneScope Scope => throw new NotImplementedException();
+ public MilestoneScope Scope { get; }
public Models.Milestone Activate(int milestoneId)
{
using (Context.BeginOperationScope())
{
- var milestone = new Milestone();
+ var milestone = GetMilestone(milestoneId, true);
+ milestone.State = MilestoneState.active;
+ return milestone.ToClientMilestone();
+ }
+ }
- if (_scope == MilestoneScope.Groups)
- {
- var group = GetGroup(_resourceId, GroupPermission.Edit);
- milestone = FindMilestone(milestoneId, group) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- }
- else if (_scope == MilestoneScope.Projects)
+ public IEnumerable GetMergeRequests(int milestoneId)
+ {
+ using (Context.BeginOperationScope())
+ {
+ var milestone = GetMilestone(milestoneId, false);
+ IEnumerable mergeRequests;
+
+ switch (Scope)
{
- var project = GetProject(_resourceId, ProjectPermission.Edit);
- milestone = FindMilestone(milestoneId, project) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
+ case MilestoneScope.Groups:
+ mergeRequests = milestone.Group.MergeRequests;
+ break;
+ case MilestoneScope.Projects:
+ mergeRequests = milestone.Project.MergeRequests;
+ break;
+ default:
+ throw new NotSupportedException($"{Scope} milestone is not supported yet.");
}
- milestone.State = MilestoneState.active;
- return milestone.ToClientMilestone();
+ mergeRequests = mergeRequests.Where(mr => mr.Milestone == milestone);
+ return mergeRequests.Select(mr => mr.ToMergeRequestClient());
}
}
@@ -67,19 +77,7 @@ public Models.Milestone Close(int milestoneId)
{
using (Context.BeginOperationScope())
{
- var milestone = new Milestone();
-
- if (_scope == MilestoneScope.Groups)
- {
- var group = GetGroup(_resourceId, GroupPermission.Edit);
- milestone = FindMilestone(milestoneId, group) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- }
- else if (_scope == MilestoneScope.Projects)
- {
- var project = GetProject(_resourceId, ProjectPermission.Edit);
- milestone = FindMilestone(milestoneId, project) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- }
-
+ var milestone = GetMilestone(milestoneId, true);
milestone.State = MilestoneState.closed;
return milestone.ToClientMilestone();
}
@@ -97,15 +95,18 @@ public Models.Milestone Create(MilestoneCreate milestone)
StartDate = string.IsNullOrEmpty(milestone.StartDate) ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(milestone.StartDate),
};
- if (_scope == MilestoneScope.Groups)
+ switch (Scope)
{
- var group = GetGroup(_resourceId, GroupPermission.Edit);
- group.Milestones.Add(ms);
- }
- else if (_scope == MilestoneScope.Projects)
- {
- var project = GetProject(_resourceId, ProjectPermission.Edit);
- project.Milestones.Add(ms);
+ case MilestoneScope.Groups:
+ var group = GetGroup(_resourceId, GroupPermission.Edit);
+ group.Milestones.Add(ms);
+ break;
+ case MilestoneScope.Projects:
+ var project = GetProject(_resourceId, ProjectPermission.Edit);
+ project.Milestones.Add(ms);
+ break;
+ default:
+ throw new NotSupportedException($"{Scope} milestone is not supported yet.");
}
return ms.ToClientMilestone();
@@ -116,17 +117,17 @@ public void Delete(int milestoneId)
{
using (Context.BeginOperationScope())
{
- if (_scope == MilestoneScope.Groups)
- {
- var group = GetGroup(_resourceId, GroupPermission.Edit);
- var milestone = FindMilestone(milestoneId, group) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- group.Milestones.Remove(milestone);
- }
- else if (_scope == MilestoneScope.Projects)
- {
- var project = GetProject(_resourceId, ProjectPermission.Edit);
- var milestone = FindMilestone(milestoneId, project) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- project.Milestones.Remove(milestone);
+ var milestone = GetMilestone(milestoneId, true);
+ switch (Scope)
+ {
+ case MilestoneScope.Groups:
+ milestone.Group.Milestones.Remove(milestone);
+ break;
+ case MilestoneScope.Projects:
+ milestone.Project.Milestones.Remove(milestone);
+ break;
+ default:
+ throw new NotSupportedException($"{Scope} milestone is not supported yet.");
}
}
}
@@ -135,17 +136,20 @@ public void Delete(int milestoneId)
{
using (Context.BeginOperationScope())
{
- IEnumerable milestones = new List();
+ IEnumerable milestones;
- if (_scope == MilestoneScope.Groups)
+ switch (Scope)
{
- var group = GetGroup(_resourceId, GroupPermission.View);
- milestones = milestones.Concat(group.Milestones);
- }
- else if (_scope == MilestoneScope.Projects)
- {
- var project = GetProject(_resourceId, ProjectPermission.View);
- milestones = milestones.Concat(project.Milestones);
+ case MilestoneScope.Groups:
+ var group = GetGroup(_resourceId, GroupPermission.View);
+ milestones = group.Milestones;
+ break;
+ case MilestoneScope.Projects:
+ var project = GetProject(_resourceId, ProjectPermission.View);
+ milestones = project.Milestones;
+ break;
+ default:
+ throw new NotSupportedException($"{Scope} milestone is not supported yet.");
}
if (query.State != null)
@@ -166,18 +170,7 @@ public Models.Milestone Update(int milestoneId, MilestoneUpdate milestone)
{
using (Context.BeginOperationScope())
{
- var ms = new Milestone();
-
- if (_scope == MilestoneScope.Groups)
- {
- var group = GetGroup(_resourceId, GroupPermission.Edit);
- ms = FindMilestone(milestoneId, group) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- }
- else if (_scope == MilestoneScope.Projects)
- {
- var project = GetProject(_resourceId, ProjectPermission.Edit);
- ms = FindMilestone(milestoneId, project) ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
- }
+ var ms = GetMilestone(milestoneId, true);
if (!string.IsNullOrEmpty(milestone.Title))
{
@@ -203,14 +196,25 @@ public Models.Milestone Update(int milestoneId, MilestoneUpdate milestone)
}
}
- private static Milestone FindMilestone(int id, Project project)
+ private Milestone GetMilestone(int milestoneId, bool editing)
{
- return project.Milestones.FirstOrDefault(x => x.Id == id);
- }
+ Milestone milestone;
- private static Milestone FindMilestone(int id, Group group)
- {
- return group.Milestones.FirstOrDefault(x => x.Id == id);
+ switch (Scope)
+ {
+ case MilestoneScope.Groups:
+ var group = GetGroup(_resourceId, editing ? GroupPermission.Edit : GroupPermission.View);
+ milestone = group.Milestones.FirstOrDefault(x => x.Id == milestoneId);
+ break;
+ case MilestoneScope.Projects:
+ var project = GetProject(_resourceId, editing ? ProjectPermission.Edit : ProjectPermission.View);
+ milestone = project.Milestones.FirstOrDefault(x => x.Id == milestoneId);
+ break;
+ default:
+ throw new NotSupportedException($"{Scope} milestone is not supported yet.");
+ }
+
+ return milestone ?? throw new GitLabNotFoundException($"Cannot find milestone with ID {milestoneId}");
}
}
}
diff --git a/NGitLab.Mock/Clients/PipelineClient.cs b/NGitLab.Mock/Clients/PipelineClient.cs
index 065e3e25..97c028c6 100644
--- a/NGitLab.Mock/Clients/PipelineClient.cs
+++ b/NGitLab.Mock/Clients/PipelineClient.cs
@@ -291,5 +291,19 @@ public GitLabCollectionResponse GetVariablesAsync(int pipeline
{
return GitLabCollectionResponse.Create(GetVariables(pipelineId));
}
+
+ public Task RetryAsync(int pipelineId, CancellationToken cancellationToken = default)
+ {
+ using (Context.BeginOperationScope())
+ {
+ var jobs = _jobClient.GetJobs(JobScopeMask.Failed).Where(j => j.Pipeline.Id == pipelineId);
+ foreach (var job in jobs)
+ {
+ _jobClient.RunAction(job.Id, JobAction.Retry);
+ }
+
+ return Task.FromResult(this[pipelineId]);
+ }
+ }
}
}
diff --git a/NGitLab.Mock/Config/GitLabGroup.cs b/NGitLab.Mock/Config/GitLabGroup.cs
index 9b88e7c4..06746af5 100644
--- a/NGitLab.Mock/Config/GitLabGroup.cs
+++ b/NGitLab.Mock/Config/GitLabGroup.cs
@@ -11,6 +11,7 @@ public GitLabGroup()
{
Labels = new GitLabLabelsCollection(this);
Permissions = new GitLabPermissionsCollection(this);
+ Milestones = new GitLabMilestonesCollection(this);
}
///
@@ -30,6 +31,8 @@ public GitLabGroup()
public GitLabLabelsCollection Labels { get; }
public GitLabPermissionsCollection Permissions { get; }
+
+ public GitLabMilestonesCollection Milestones { get; }
}
public class GitLabGroupsCollection : GitLabCollection
diff --git a/NGitLab.Mock/Config/GitLabHelpers.cs b/NGitLab.Mock/Config/GitLabHelpers.cs
index 005fa3cf..1f43107f 100644
--- a/NGitLab.Mock/Config/GitLabHelpers.cs
+++ b/NGitLab.Mock/Config/GitLabHelpers.cs
@@ -596,8 +596,9 @@ public static GitLabProject WithMergeRequest(this GitLabProject project, string?
/// Merge date time.
/// Approvers usernames.
/// Labels names.
+ /// Milestone name.
/// Configuration method
- public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch = null, string? title = null, int id = default, string? targetBranch = null, string? description = null, string? author = null, string? assignee = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, DateTime? mergedAt = null, IEnumerable? approvers = null, IEnumerable? labels = null, Action? configure = null)
+ public static GitLabProject WithMergeRequest(this GitLabProject project, string? sourceBranch = null, string? title = null, int id = default, string? targetBranch = null, string? description = null, string? author = null, string? assignee = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, DateTime? mergedAt = null, IEnumerable? approvers = null, IEnumerable? labels = null, string? milestone = null, Action? configure = null)
{
return WithMergeRequest(project, sourceBranch, title, author, mergeRequest =>
{
@@ -611,6 +612,8 @@ public static GitLabProject WithMergeRequest(this GitLabProject project, string?
mergeRequest.UpdatedAt = updatedAt;
mergeRequest.ClosedAt = closedAt;
mergeRequest.MergedAt = mergedAt;
+ mergeRequest.Milestone = milestone;
+
if (labels != null)
{
foreach (var label in labels)
@@ -737,6 +740,57 @@ public static GitLabProject WithGroupPermission(this GitLabProject project, stri
});
}
+ ///
+ /// Add milestone in group
+ ///
+ /// Group.
+ /// Title (required)
+ /// Configuration method
+ public static GitLabGroup WithMilestone(this GitLabGroup group, string title, Action configure)
+ {
+ return Configure(group, _ =>
+ {
+ var milestone = new GitLabMilestone
+ {
+ Title = title ?? throw new ArgumentNullException(nameof(title)),
+ };
+
+ group.Milestones.Add(milestone);
+ configure(milestone);
+ });
+ }
+
+ ///
+ /// Add milestone in group
+ ///
+ /// Group.
+ /// Title (required)
+ /// Explicit ID (config increment)
+ /// Description.
+ /// Due date time.
+ /// Start date time.
+ /// Creation date time.
+ /// Update date time.
+ /// Close date time.
+ /// Configuration method
+ public static GitLabGroup WithMilestone(this GitLabGroup group, string title, int id = default, string? description = null, DateTime? dueDate = null, DateTime? startDate = null, DateTime? createdAt = null, DateTime? updatedAt = null, DateTime? closedAt = null, Action? configure = null)
+ {
+ return WithMilestone(group, title, milestone =>
+ {
+ if (id != default)
+ milestone.Id = id;
+
+ milestone.Description = description;
+ milestone.DueDate = dueDate;
+ milestone.StartDate = startDate;
+ milestone.CreatedAt = createdAt;
+ milestone.UpdatedAt = updatedAt;
+ milestone.ClosedAt = closedAt;
+
+ configure?.Invoke(milestone);
+ });
+ }
+
///
/// Add milestone in project
///
@@ -783,6 +837,8 @@ public static GitLabProject WithMilestone(this GitLabProject project, string tit
milestone.CreatedAt = createdAt;
milestone.UpdatedAt = updatedAt;
milestone.ClosedAt = closedAt;
+
+ configure?.Invoke(milestone);
});
}
@@ -1108,6 +1164,11 @@ private static void CreateGroup(GitLabServer server, GitLabGroup group)
{
CreatePermission(server, grp, permission);
}
+
+ foreach (var milestone in group.Milestones)
+ {
+ CreateMilestone(grp, milestone);
+ }
}
private static void CreateProject(GitLabServer server, GitLabProject project)
@@ -1401,6 +1462,7 @@ private static void CreateMergeRequest(GitLabServer server, Project project, Git
ClosedAt = mergeRequest.ClosedAt,
MergedAt = mergeRequest.MergedAt,
SourceProject = project,
+ Milestone = string.IsNullOrEmpty(mergeRequest.Milestone) ? null : GetOrCreateMilestone(project, mergeRequest.Milestone),
};
var endedAt = request.MergedAt ?? request.ClosedAt;
@@ -1453,7 +1515,19 @@ private static Permission CreatePermission(GitLabServer server, GitLabPermission
: new Permission(GetOrCreateUser(server, permission.User), permission.Level);
}
+ private static void CreateMilestone(Group group, GitLabMilestone milestone)
+ {
+ var mlt = CreateMilestone(milestone);
+ group.Milestones.Add(mlt);
+ }
+
private static void CreateMilestone(Project project, GitLabMilestone milestone)
+ {
+ var mlt = CreateMilestone(milestone);
+ project.Milestones.Add(mlt);
+ }
+
+ private static Milestone CreateMilestone(GitLabMilestone milestone)
{
var mlt = new Milestone
{
@@ -1465,10 +1539,13 @@ private static void CreateMilestone(Project project, GitLabMilestone milestone)
UpdatedAt = milestone.UpdatedAt ?? DateTimeOffset.UtcNow,
ClosedAt = milestone.ClosedAt,
};
- project.Milestones.Add(mlt);
if (mlt.ClosedAt != null && mlt.UpdatedAt > mlt.ClosedAt)
+ {
mlt.UpdatedAt = (DateTimeOffset)mlt.ClosedAt;
+ }
+
+ return mlt;
}
private static void CreateComment(GitLabServer server, Issue issue, GitLabComment comment)
@@ -1624,6 +1701,14 @@ private static User GetOrCreateUser(GitLabServer server, string username)
private static Milestone GetOrCreateMilestone(Project project, string title)
{
var milestone = project.Milestones.FirstOrDefault(x => string.Equals(x.Title, title, StringComparison.OrdinalIgnoreCase));
+
+ var group = project.Group;
+ while (milestone == null && group != null)
+ {
+ milestone = group.Milestones.FirstOrDefault(x => string.Equals(x.Title, title, StringComparison.OrdinalIgnoreCase));
+ group = group.Parent;
+ }
+
if (milestone == null)
{
milestone = new Milestone
diff --git a/NGitLab.Mock/Config/GitLabMergeRequest.cs b/NGitLab.Mock/Config/GitLabMergeRequest.cs
index 3b48152d..d07f720d 100644
--- a/NGitLab.Mock/Config/GitLabMergeRequest.cs
+++ b/NGitLab.Mock/Config/GitLabMergeRequest.cs
@@ -53,6 +53,8 @@ public GitLabMergeRequest()
public DateTime? MergedAt { get; set; }
public DateTime? ClosedAt { get; set; }
+
+ public string Milestone { get; set; }
}
public class GitLabMergeRequestsCollection : GitLabCollection
diff --git a/NGitLab.Mock/Config/GitLabMilestone.cs b/NGitLab.Mock/Config/GitLabMilestone.cs
index f6869085..f8a108bd 100644
--- a/NGitLab.Mock/Config/GitLabMilestone.cs
+++ b/NGitLab.Mock/Config/GitLabMilestone.cs
@@ -34,6 +34,11 @@ internal GitLabMilestonesCollection(GitLabProject parent)
{
}
+ internal GitLabMilestonesCollection(GitLabGroup parent)
+ : base(parent)
+ {
+ }
+
internal override void SetItem(GitLabMilestone item)
{
if (item == null)
diff --git a/NGitLab.Mock/Group.cs b/NGitLab.Mock/Group.cs
index fb9908b8..aea0de2d 100644
--- a/NGitLab.Mock/Group.cs
+++ b/NGitLab.Mock/Group.cs
@@ -76,6 +76,8 @@ public string Name
public MilestoneCollection Milestones { get; }
+ public IEnumerable MergeRequests => AllProjects.SelectMany(project => project.MergeRequests);
+
public string Path
{
get
@@ -115,13 +117,15 @@ public IEnumerable DescendantGroups
public IEnumerable AllProjects => Projects.Concat(DescendantGroups.SelectMany(group => group.Projects));
- public EffectivePermissions GetEffectivePermissions()
+ public EffectivePermissions GetEffectivePermissions() => GetEffectivePermissions(includeInheritedPermissions: true);
+
+ public EffectivePermissions GetEffectivePermissions(bool includeInheritedPermissions)
{
var result = new Dictionary();
- if (Parent != null)
+ if (Parent != null && includeInheritedPermissions)
{
- foreach (var effectivePermission in Parent.GetEffectivePermissions().Permissions)
+ foreach (var effectivePermission in Parent.GetEffectivePermissions(includeInheritedPermissions).Permissions)
{
Add(effectivePermission.User, effectivePermission.AccessLevel);
}
@@ -135,7 +139,7 @@ public EffectivePermissions GetEffectivePermissions()
}
else
{
- foreach (var effectivePermission in permission.Group.GetEffectivePermissions().Permissions)
+ foreach (var effectivePermission in permission.Group.GetEffectivePermissions(includeInheritedPermissions).Permissions)
{
Add(effectivePermission.User, effectivePermission.AccessLevel);
}
@@ -254,5 +258,22 @@ public Models.Group ToClientGroup(User currentUser)
SharedRunnersMinutesLimit = (int)SharedRunnersLimit.TotalMinutes,
};
}
+
+ ///
+ /// https://docs.gitlab.com/ee/user/group/settings/group_access_tokens.html#bot-users-for-groups
+ ///
+ /// AccessLevel to give to the bot user
+ /// Bot user that have been added to the group
+ public User CreateBotUser(AccessLevel accessLevel)
+ {
+ var botUsername = $"group_{Id}_bot_{Guid.NewGuid():D}";
+ var bot = new User(botUsername)
+ {
+ Email = $"{botUsername}@noreply.example.com",
+ };
+ Permissions.Add(new Permission(bot, accessLevel));
+ Server.Users.Add(bot);
+ return bot;
+ }
}
}
diff --git a/NGitLab.Mock/MergeRequest.cs b/NGitLab.Mock/MergeRequest.cs
index a06e9a48..d48238bc 100644
--- a/NGitLab.Mock/MergeRequest.cs
+++ b/NGitLab.Mock/MergeRequest.cs
@@ -147,6 +147,8 @@ public Pipeline HeadPipeline
}
}
+ public Milestone Milestone { get; set; }
+
public IList Labels { get; } = new List();
public NoteCollection Comments { get; }
diff --git a/NGitLab.Mock/Milestone.cs b/NGitLab.Mock/Milestone.cs
index 15bca089..8a652278 100644
--- a/NGitLab.Mock/Milestone.cs
+++ b/NGitLab.Mock/Milestone.cs
@@ -7,6 +7,8 @@ public sealed class Milestone : GitLabObject
{
public Project Project => Parent as Project;
+ public Group Group => Parent as Group;
+
public int Id { get; set; }
public int Iid { get; set; }
diff --git a/NGitLab.Mock/NGitLab.Mock.csproj b/NGitLab.Mock/NGitLab.Mock.csproj
index 4bb1a565..d44f59d7 100644
--- a/NGitLab.Mock/NGitLab.Mock.csproj
+++ b/NGitLab.Mock/NGitLab.Mock.csproj
@@ -1,22 +1,22 @@
- net461;netstandard2.0
- $(NoWarn);NU5104
+ net472;netstandard2.0
+ $(NoWarn);NU1701;NU5104
GitLab REST API .NET Client Mock Library
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/NGitLab.Mock/Project.cs b/NGitLab.Mock/Project.cs
index 197e07e8..86f99ba9 100644
--- a/NGitLab.Mock/Project.cs
+++ b/NGitLab.Mock/Project.cs
@@ -68,7 +68,7 @@ public string DefaultBranch
public Project ForkedFrom { get; internal set; }
- public RepositoryAccessLevel ForkingAccessLevel { get; set; }
+ public RepositoryAccessLevel ForkingAccessLevel { get; set; } = RepositoryAccessLevel.Enabled;
public string ImportStatus { get; set; }
@@ -154,13 +154,18 @@ public void Remove()
Group.Projects.Remove(this);
}
- public EffectivePermissions GetEffectivePermissions()
+ public EffectivePermissions GetEffectivePermissions() => GetEffectivePermissions(includeInheritedPermissions: true);
+
+ public EffectivePermissions GetEffectivePermissions(bool includeInheritedPermissions)
{
var result = new Dictionary();
- foreach (var effectivePermission in Group.GetEffectivePermissions().Permissions)
+ if (includeInheritedPermissions)
{
- Add(effectivePermission.User, effectivePermission.AccessLevel);
+ foreach (var effectivePermission in Group.GetEffectivePermissions(includeInheritedPermissions).Permissions)
+ {
+ Add(effectivePermission.User, effectivePermission.AccessLevel);
+ }
}
foreach (var permission in Permissions)
@@ -171,7 +176,7 @@ public EffectivePermissions GetEffectivePermissions()
}
else
{
- foreach (var effectivePermission in permission.Group.GetEffectivePermissions().Permissions)
+ foreach (var effectivePermission in permission.Group.GetEffectivePermissions(includeInheritedPermissions).Permissions)
{
Add(effectivePermission.User, effectivePermission.AccessLevel);
}
@@ -399,6 +404,25 @@ public Project Fork(Group group, User user, string projectName)
return newProject;
}
+ ///
+ /// https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html#bot-users-for-projects
+ ///
+ /// Name of the token
+ /// AccessLevel to give to the bot user
+ /// Bot user that have been added to the project
+ public User CreateBotUser(string tokenName, AccessLevel accessLevel)
+ {
+ var botUsername = $"project_{Id}_bot_{Guid.NewGuid():D}";
+ var bot = new User(botUsername)
+ {
+ Name = tokenName,
+ Email = $"{botUsername}@noreply.example.com",
+ };
+ Permissions.Add(new Permission(bot, accessLevel));
+ Server.Users.Add(bot);
+ return bot;
+ }
+
public Models.Project ToClientProject(User currentUser)
{
var kind = Group.IsUserNamespace ? "user" : "group";
diff --git a/NGitLab.Mock/PublicAPI.Unshipped.txt b/NGitLab.Mock/PublicAPI.Unshipped.txt
index 02648e5f..2aa6ffc9 100644
--- a/NGitLab.Mock/PublicAPI.Unshipped.txt
+++ b/NGitLab.Mock/PublicAPI.Unshipped.txt
@@ -1,4 +1,4 @@
-abstract NGitLab.Mock.File.Content.get -> byte[]
+abstract NGitLab.Mock.File.Content.get -> byte[]
abstract NGitLab.Mock.Note.NoteableType.get -> string
abstract NGitLab.Mock.Note.NoticableId.get -> int
abstract NGitLab.Mock.Note.NoticableIid.get -> int
@@ -134,6 +134,7 @@ NGitLab.Mock.Config.GitLabConfig.DefaultVisibility.set -> void
NGitLab.Mock.Config.GitLabConfig.Serialize() -> string
NGitLab.Mock.Config.GitLabConfig.Url.get -> string
NGitLab.Mock.Config.GitLabConfig.Url.set -> void
+NGitLab.Mock.Config.GitLabGroup.Milestones.get -> NGitLab.Mock.Config.GitLabMilestonesCollection
NGitLab.Mock.Config.GitLabGroup.Visibility.get -> NGitLab.Models.VisibilityLevel?
NGitLab.Mock.Config.GitLabIssue.Comments.get -> NGitLab.Mock.Config.GitLabCommentsCollection
NGitLab.Mock.Config.GitLabIssue.CreatedAt.get -> System.DateTime?
@@ -163,6 +164,8 @@ NGitLab.Mock.Config.GitLabJob.TagList.set -> void
NGitLab.Mock.Config.GitLabJobsCollection
NGitLab.Mock.Config.GitLabMergeRequest.Comments.get -> NGitLab.Mock.Config.GitLabCommentsCollection
NGitLab.Mock.Config.GitLabMergeRequest.CreatedAt.get -> System.DateTime?
+NGitLab.Mock.Config.GitLabMergeRequest.Milestone.get -> string
+NGitLab.Mock.Config.GitLabMergeRequest.Milestone.set -> void
NGitLab.Mock.Config.GitLabMergeRequest.UpdatedAt.get -> System.DateTime?
NGitLab.Mock.Config.GitLabMilestone
NGitLab.Mock.Config.GitLabMilestone.ClosedAt.get -> System.DateTime?
@@ -429,6 +432,7 @@ NGitLab.Mock.Group.CanUserAddProject(NGitLab.Mock.User user) -> bool
NGitLab.Mock.Group.CanUserDeleteGroup(NGitLab.Mock.User user) -> bool
NGitLab.Mock.Group.CanUserEditGroup(NGitLab.Mock.User user) -> bool
NGitLab.Mock.Group.CanUserViewGroup(NGitLab.Mock.User user) -> bool
+NGitLab.Mock.Group.CreateBotUser(NGitLab.Models.AccessLevel accessLevel) -> NGitLab.Mock.User
NGitLab.Mock.Group.DescendantGroups.get -> System.Collections.Generic.IEnumerable
NGitLab.Mock.Group.Description.get -> string
NGitLab.Mock.Group.Description.set -> void
@@ -436,6 +440,7 @@ NGitLab.Mock.Group.ExtraSharedRunnersLimit.get -> System.TimeSpan
NGitLab.Mock.Group.ExtraSharedRunnersLimit.set -> void
NGitLab.Mock.Group.FullName.get -> string
NGitLab.Mock.Group.GetEffectivePermissions() -> NGitLab.Mock.EffectivePermissions
+NGitLab.Mock.Group.GetEffectivePermissions(bool includeInheritedPermissions) -> NGitLab.Mock.EffectivePermissions
NGitLab.Mock.Group.Group() -> void
NGitLab.Mock.Group.Group(NGitLab.Mock.User user) -> void
NGitLab.Mock.Group.Group(string name) -> void
@@ -447,6 +452,7 @@ NGitLab.Mock.Group.IsUserOwner(NGitLab.Mock.User user) -> bool
NGitLab.Mock.Group.Labels.get -> NGitLab.Mock.LabelsCollection
NGitLab.Mock.Group.LfsEnabled.get -> bool
NGitLab.Mock.Group.LfsEnabled.set -> void
+NGitLab.Mock.Group.MergeRequests.get -> System.Collections.Generic.IEnumerable
NGitLab.Mock.Group.Milestones.get -> NGitLab.Mock.MilestoneCollection
NGitLab.Mock.Group.Name.get -> string
NGitLab.Mock.Group.Name.set -> void
@@ -617,6 +623,8 @@ NGitLab.Mock.MergeRequest.MergedAt.set -> void
NGitLab.Mock.MergeRequest.MergeRequest() -> void
NGitLab.Mock.MergeRequest.MergeWhenPipelineSucceeds.get -> bool
NGitLab.Mock.MergeRequest.MergeWhenPipelineSucceeds.set -> void
+NGitLab.Mock.MergeRequest.Milestone.get -> NGitLab.Mock.Milestone
+NGitLab.Mock.MergeRequest.Milestone.set -> void
NGitLab.Mock.MergeRequest.Project.get -> NGitLab.Mock.Project
NGitLab.Mock.MergeRequest.Rebase(NGitLab.Mock.User user) -> NGitLab.Models.RebaseResult
NGitLab.Mock.MergeRequest.RebaseInProgress.get -> bool
@@ -663,6 +671,7 @@ NGitLab.Mock.Milestone.Description.get -> string
NGitLab.Mock.Milestone.Description.set -> void
NGitLab.Mock.Milestone.DueDate.get -> System.DateTimeOffset
NGitLab.Mock.Milestone.DueDate.set -> void
+NGitLab.Mock.Milestone.Group.get -> NGitLab.Mock.Group
NGitLab.Mock.Milestone.Id.get -> int
NGitLab.Mock.Milestone.Id.set -> void
NGitLab.Mock.Milestone.Iid.get -> int
@@ -792,6 +801,7 @@ NGitLab.Mock.Project.CanUserViewConfidentialIssues(NGitLab.Mock.User user) -> bo
NGitLab.Mock.Project.CanUserViewProject(NGitLab.Mock.User user) -> bool
NGitLab.Mock.Project.CommitInfos.get -> NGitLab.Mock.CommitInfoCollection
NGitLab.Mock.Project.CommitStatuses.get -> NGitLab.Mock.CommitStatusCollection
+NGitLab.Mock.Project.CreateBotUser(string tokenName, NGitLab.Models.AccessLevel accessLevel) -> NGitLab.Mock.User
NGitLab.Mock.Project.CreateMergeRequest(NGitLab.Mock.User user, string title, string description, string targetBranchName, string sourceBranchName, NGitLab.Mock.Project sourceProject = null) -> NGitLab.Mock.MergeRequest
NGitLab.Mock.Project.CreateNewMergeRequest(NGitLab.Mock.User user, string branchName, string targetBranch, string title, string description) -> NGitLab.Mock.MergeRequest
NGitLab.Mock.Project.DefaultBranch.get -> string
@@ -806,6 +816,7 @@ NGitLab.Mock.Project.ForkingAccessLevel.get -> NGitLab.Models.RepositoryAccessLe
NGitLab.Mock.Project.ForkingAccessLevel.set -> void
NGitLab.Mock.Project.FullName.get -> string
NGitLab.Mock.Project.GetEffectivePermissions() -> NGitLab.Mock.EffectivePermissions
+NGitLab.Mock.Project.GetEffectivePermissions(bool includeInheritedPermissions) -> NGitLab.Mock.EffectivePermissions
NGitLab.Mock.Project.Group.get -> NGitLab.Mock.Group
NGitLab.Mock.Project.Hooks.get -> NGitLab.Mock.ProjectHookCollection
NGitLab.Mock.Project.HttpUrl.get -> string
@@ -1119,6 +1130,7 @@ NGitLab.Mock.TextFile.TextFile(string path, string content, System.Text.Encoding
NGitLab.Mock.User
NGitLab.Mock.User.AvatarUrl.get -> string
NGitLab.Mock.User.AvatarUrl.set -> void
+NGitLab.Mock.User.Bot.get -> bool
NGitLab.Mock.User.CreatedAt.get -> System.DateTime
NGitLab.Mock.User.CreatedAt.set -> void
NGitLab.Mock.User.Email.get -> string
@@ -1215,8 +1227,10 @@ static NGitLab.Mock.Config.GitLabHelpers.WithIssue(this NGitLab.Mock.Config.GitL
static NGitLab.Mock.Config.GitLabHelpers.WithIssue(this NGitLab.Mock.Config.GitLabProject project, string title, string author, System.Action configure) -> NGitLab.Mock.Config.GitLabProject
static NGitLab.Mock.Config.GitLabHelpers.WithJob(this NGitLab.Mock.Config.GitLabPipeline pipeline, string name = null, string stage = null, NGitLab.JobStatus status = NGitLab.JobStatus.Unknown, System.DateTime? createdAt = null, System.DateTime? startedAt = null, System.DateTime? finishedAt = null, bool allowFailure = false, NGitLab.Mock.Config.GitLabPipeline downstreamPipeline = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabPipeline
static NGitLab.Mock.Config.GitLabHelpers.WithMergeCommit(this NGitLab.Mock.Config.GitLabProject project, string sourceBranch, string targetBranch = null, string user = null, bool deleteSourceBranch = false, System.Collections.Generic.IEnumerable tags = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabProject
-static NGitLab.Mock.Config.GitLabHelpers.WithMergeRequest(this NGitLab.Mock.Config.GitLabProject project, string sourceBranch = null, string title = null, int id = 0, string targetBranch = null, string description = null, string author = null, string assignee = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null, System.DateTime? closedAt = null, System.DateTime? mergedAt = null, System.Collections.Generic.IEnumerable approvers = null, System.Collections.Generic.IEnumerable labels = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabProject
+static NGitLab.Mock.Config.GitLabHelpers.WithMergeRequest(this NGitLab.Mock.Config.GitLabProject project, string sourceBranch = null, string title = null, int id = 0, string targetBranch = null, string description = null, string author = null, string assignee = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null, System.DateTime? closedAt = null, System.DateTime? mergedAt = null, System.Collections.Generic.IEnumerable approvers = null, System.Collections.Generic.IEnumerable labels = null, string milestone = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabProject
static NGitLab.Mock.Config.GitLabHelpers.WithMergeRequest(this NGitLab.Mock.Config.GitLabProject project, string sourceBranch, string title, string author, System.Action configure) -> NGitLab.Mock.Config.GitLabProject
+static NGitLab.Mock.Config.GitLabHelpers.WithMilestone(this NGitLab.Mock.Config.GitLabGroup group, string title, int id = 0, string description = null, System.DateTime? dueDate = null, System.DateTime? startDate = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null, System.DateTime? closedAt = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabGroup
+static NGitLab.Mock.Config.GitLabHelpers.WithMilestone(this NGitLab.Mock.Config.GitLabGroup group, string title, System.Action configure) -> NGitLab.Mock.Config.GitLabGroup
static NGitLab.Mock.Config.GitLabHelpers.WithMilestone(this NGitLab.Mock.Config.GitLabProject project, string title, int id = 0, string description = null, System.DateTime? dueDate = null, System.DateTime? startDate = null, System.DateTime? createdAt = null, System.DateTime? updatedAt = null, System.DateTime? closedAt = null, System.Action configure = null) -> NGitLab.Mock.Config.GitLabProject
static NGitLab.Mock.Config.GitLabHelpers.WithMilestone(this NGitLab.Mock.Config.GitLabProject project, string title, System.Action configure) -> NGitLab.Mock.Config.GitLabProject
static NGitLab.Mock.Config.GitLabHelpers.WithPipeline(this NGitLab.Mock.Config.GitLabProject project, string ref, System.Action configure) -> NGitLab.Mock.Config.GitLabProject
diff --git a/NGitLab.Mock/Repository.cs b/NGitLab.Mock/Repository.cs
index 11b07f48..a6c9372c 100644
--- a/NGitLab.Mock/Repository.cs
+++ b/NGitLab.Mock/Repository.cs
@@ -502,7 +502,7 @@ public FileData GetFile(string filePath, string @ref)
Ref = @ref,
BlobId = ((Blob)commit[filePath].Target).Id.ToString(),
CommitId = commit.Sha,
- LastCommitId = repo.GetLastCommitForFileChanges(filePath).Sha,
+ LastCommitId = repo.GetLastCommitForFileChanges(filePath)?.Sha,
};
}
diff --git a/NGitLab.Mock/User.cs b/NGitLab.Mock/User.cs
index a42436f8..a5d2f8d7 100644
--- a/NGitLab.Mock/User.cs
+++ b/NGitLab.Mock/User.cs
@@ -34,6 +34,25 @@ public User(string userName)
public string WebUrl => Server.MakeUrl(UserName);
+ public bool Bot
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(UserName))
+ return false;
+
+ var nameParts = UserName.Split('_');
+
+ if (nameParts.Length != 4)
+ return false;
+
+ if (!string.Equals(nameParts[0], "project", StringComparison.Ordinal) && !string.Equals(nameParts[0], "group", StringComparison.Ordinal))
+ return false;
+
+ return int.TryParse(nameParts[1], out var _) && string.Equals("bot", nameParts[2], StringComparison.Ordinal);
+ }
+ }
+
public Models.User ToClientUser()
{
var user = new Models.User();
@@ -59,6 +78,7 @@ private void CopyTo(T instance)
instance.AvatarURL = AvatarUrl;
instance.CreatedAt = CreatedAt;
instance.Identities = Identities;
+ instance.Bot = Bot;
if (IsAdmin)
{
diff --git a/NGitLab.Tests/CommitsTests.cs b/NGitLab.Tests/CommitsTests.cs
index 356965b3..b77dd7d5 100644
--- a/NGitLab.Tests/CommitsTests.cs
+++ b/NGitLab.Tests/CommitsTests.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NGitLab.Models;
@@ -72,7 +73,10 @@ public async Task Test_can_get_merge_request_associated_to_commit()
Title = mergeRequestTitle,
});
- var mergeRequests = context.Client.GetCommits(project.Id).GetRelatedMergeRequestsAsync(new RelatedMergeRequestsQuery { Sha = commit.Id });
+ var mergeRequests = await GitLabTestContext.RetryUntilAsync(
+ () => context.Client.GetCommits(project.Id).GetRelatedMergeRequestsAsync(new RelatedMergeRequestsQuery { Sha = commit.Id }),
+ mergeRequests => mergeRequests.Any(),
+ TimeSpan.FromSeconds(10));
var mergeRequest = mergeRequests.Single();
Assert.AreEqual(mergeRequestTitle, mergeRequest.Title);
diff --git a/NGitLab.Tests/Docker/GitLabDockerContainer.cs b/NGitLab.Tests/Docker/GitLabDockerContainer.cs
index 5561ed8a..faa595f7 100644
--- a/NGitLab.Tests/Docker/GitLabDockerContainer.cs
+++ b/NGitLab.Tests/Docker/GitLabDockerContainer.cs
@@ -27,7 +27,7 @@ public class GitLabDockerContainer
public const string ImageName = "gitlab/gitlab-ee";
// https://hub.docker.com/r/gitlab/gitlab-ee/tags/
- public const string GitLabDockerVersion = "14.10.5-ee.0"; // Keep in sync with .github/workflows/ci.yml
+ public const string GitLabDockerVersion = "15.4.6-ee.0"; // Keep in sync with .github/workflows/ci.yml
private static string s_creationErrorMessage;
private static readonly SemaphoreSlim s_setupLock = new(initialCount: 1, maxCount: 1);
@@ -107,6 +107,10 @@ private static async Task ValidateDockerIsEnabled(DockerClient client)
{
await client.Images.ListImagesAsync(new ImagesListParameters()).ConfigureAwait(false);
}
+ catch (ArgumentOutOfRangeException ex) when (ex.Message.StartsWith("The added or subtracted value results in an un-representable DateTime.", StringComparison.Ordinal))
+ {
+ // Ignore https://github.com/rancher-sandbox/rancher-desktop/issues/5145
+ }
catch (Exception ex)
{
s_creationErrorMessage = "Cannot connect to Docker service. Make sure it's running on your machine before launching any tests.\nDetails: " + ex;
diff --git a/NGitLab.Tests/Docker/GitLabTestContext.cs b/NGitLab.Tests/Docker/GitLabTestContext.cs
index be631d67..421c64cd 100644
--- a/NGitLab.Tests/Docker/GitLabTestContext.cs
+++ b/NGitLab.Tests/Docker/GitLabTestContext.cs
@@ -199,10 +199,10 @@ public Group CreateGroup(Action configure = null)
return client.Groups.Create(groupCreate);
}
- public (Project Project, MergeRequest MergeRequest) CreateMergeRequest()
+ public (Project Project, MergeRequest MergeRequest) CreateMergeRequest(Action configure = null, Action configureProject = null)
{
var client = Client;
- var project = CreateProject(initializeWithCommits: true);
+ var project = CreateProject(configureProject, initializeWithCommits: true);
const string BranchForMRName = "branch-for-mr";
s_gitlabRetryPolicy.Execute(() => client.GetRepository(project.Id).Files.Create(new FileUpsert { Branch = project.DefaultBranch, CommitMessage = "test", Content = "test", Path = "test.md" }));
@@ -224,12 +224,15 @@ public Group CreateGroup(Action configure = null)
s_gitlabRetryPolicy.Execute(() => client.GetRepository(project.Id).Files.Update(new FileUpsert { Branch = BranchForMRName, CommitMessage = "test", Content = "test2", Path = "test.md" }));
- var mr = client.GetMergeRequest(project.Id).Create(new MergeRequestCreate
+ var mergeRequestCreate = new MergeRequestCreate
{
SourceBranch = BranchForMRName,
TargetBranch = project.DefaultBranch,
Title = "test",
- });
+ };
+
+ configure?.Invoke(mergeRequestCreate);
+ var mr = client.GetMergeRequest(project.Id).Create(mergeRequestCreate);
return (project, mr);
}
@@ -255,13 +258,20 @@ public int GetRandomNumber()
return RandomNumberGenerator.GetInt32(0, int.MaxValue);
}
- public void ReportTestAsInconclusiveIfVersionOutOfRange(VersionRange versionRange)
+ public bool IsGitLabVersionInRange(VersionRange versionRange, out string gitLabVersion)
+ {
+ var currentVersion = Client.Version.Get();
+ gitLabVersion = currentVersion.Version;
+
+ // Although a GitLab version is not a NuGet version, let's consider it as one to determine range inclusion
+ return NuGetVersion.TryParse(gitLabVersion, out var nuGetVersion) &&
+ versionRange.Satisfies(nuGetVersion);
+ }
+
+ public void ReportTestAsInconclusiveIfGitLabVersionOutOfRange(VersionRange versionRange)
{
- var gitLabVersion = Client.Version.Get();
- if (!NuGetVersion.TryParse(gitLabVersion.Version, out var currentVersion))
- return;
- if (!versionRange.Satisfies(currentVersion))
- Assert.Inconclusive($"Test supported in version range '{versionRange}', but currently running against '{currentVersion}'");
+ if (!IsGitLabVersionInRange(versionRange, out var gitLabVersion))
+ Assert.Inconclusive($"Test supported by GitLab '{versionRange}', but currently running against '{gitLabVersion}'");
}
private IGitLabClient CreateClient(string token)
diff --git a/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs b/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs
index 32d10a47..f7d45b65 100644
--- a/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs
+++ b/NGitLab.Tests/Docker/GitLabTestContextRequestOptions.cs
@@ -199,13 +199,21 @@ private static void LogHeaders(StringBuilder sb, WebHeaderCollection headers)
foreach (var headerValue in headerValues)
{
+ sb.Append(headerName).Append(": ");
if (string.Equals(headerName, "Private-Token", StringComparison.OrdinalIgnoreCase))
{
- sb.Append("Private-Token").Append(": ****** ").AppendLine();
+ sb.AppendLine("******");
+ }
+ else if (string.Equals(headerName, "Authorization", StringComparison.OrdinalIgnoreCase))
+ {
+ const string BearerTokenPrefix = "Bearer ";
+ if (headerValue.StartsWith(BearerTokenPrefix, StringComparison.Ordinal))
+ sb.Append(BearerTokenPrefix);
+ sb.AppendLine("******");
}
else
{
- sb.Append(headerName).Append(": ").Append(headerValue).AppendLine();
+ sb.AppendLine(headerValue);
}
}
}
diff --git a/NGitLab.Tests/HttpRequestorTests.cs b/NGitLab.Tests/HttpRequestorTests.cs
index 85288e50..83b9a515 100644
--- a/NGitLab.Tests/HttpRequestorTests.cs
+++ b/NGitLab.Tests/HttpRequestorTests.cs
@@ -125,6 +125,22 @@ public async Task Test_impersonation_via_sudo_and_user_id()
Assert.AreEqual(commonUserSession.Id, issue.Author.Id);
}
+ [Test]
+ public async Task Test_authorization_header_uses_bearer()
+ {
+ // Arrange
+ using var context = await GitLabTestContext.CreateAsync();
+ var commonUserClient = context.Client;
+ string expectedHeaderValue = string.Concat("Bearer ", context.DockerContainer.Credentials.UserToken);
+
+ // Act
+ var project = commonUserClient.Projects.Accessible.First();
+
+ // Assert
+ var actualHeaderValue = context.LastRequest.Headers[HttpRequestHeader.Authorization];
+ Assert.AreEqual(expectedHeaderValue, actualHeaderValue);
+ }
+
private sealed class MockRequestOptions : RequestOptions
{
public string HttpRequestSudoHeader { get; set; }
diff --git a/NGitLab.Tests/IssueTests.cs b/NGitLab.Tests/IssueTests.cs
index b82e1113..cb975f6d 100644
--- a/NGitLab.Tests/IssueTests.cs
+++ b/NGitLab.Tests/IssueTests.cs
@@ -293,5 +293,23 @@ public async Task Test_get_new_and_updated_issue_with_duedate()
Assert.AreEqual(updatedDueDate, updatedIssue.DueDate);
}
+
+ [Test]
+ [NGitLabRetry]
+ public async Task Test_get_linked_issue()
+ {
+ using var context = await GitLabTestContext.CreateAsync();
+ var project = context.CreateProject();
+ var issuesClient = context.Client.Issues;
+
+ var issue1 = await issuesClient.CreateAsync(new IssueCreate { ProjectId = project.Id, Title = "title1" });
+ var issue2 = await issuesClient.CreateAsync(new IssueCreate { ProjectId = project.Id, Title = "title2", Description = "related to #1" });
+ var linked = issuesClient.CreateLinkBetweenIssues(project.Id, issue1.IssueId, project.Id, issue2.IssueId);
+ Assert.IsTrue(linked, "Expected true for create Link between issues");
+ var issues = issuesClient.LinkedToAsync(project.Id, issue1.IssueId).ToList();
+
+ // for now, no API to link issues so not links exist but API should not throw
+ Assert.AreEqual(1, issues.Count, "Expected 1. Got {0}", issues.Count);
+ }
}
}
diff --git a/NGitLab.Tests/JobTests.cs b/NGitLab.Tests/JobTests.cs
index 4911446c..247cdf6b 100644
--- a/NGitLab.Tests/JobTests.cs
+++ b/NGitLab.Tests/JobTests.cs
@@ -10,7 +10,7 @@ namespace NGitLab.Tests
{
public class JobTests
{
- internal static void AddGitLabCiFile(IGitLabClient client, Project project, int jobCount = 1, bool manualAction = false, string branch = null)
+ internal static void AddGitLabCiFile(IGitLabClient client, Project project, int jobCount = 1, bool manualAction = false, string branch = null, bool pipelineSucceeds = true)
{
var content = @"
variables:
@@ -24,6 +24,7 @@ internal static void AddGitLabCiFile(IGitLabClient client, Project project, int
script:
- echo test
- echo test > file{i.ToString(CultureInfo.InvariantCulture)}.txt
+ - exit {(pipelineSucceeds ? "0" : "1")}
artifacts:
paths:
- '*.txt'
diff --git a/NGitLab.Tests/MergeRequest/MergeRequestChangesClientTests.cs b/NGitLab.Tests/MergeRequest/MergeRequestChangesClientTests.cs
index fcb44a07..90f96152 100644
--- a/NGitLab.Tests/MergeRequest/MergeRequestChangesClientTests.cs
+++ b/NGitLab.Tests/MergeRequest/MergeRequestChangesClientTests.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Linq;
using System.Threading.Tasks;
using NGitLab.Tests.Docker;
using NUnit.Framework;
@@ -14,7 +16,12 @@ public async Task GetChangesOnMergeRequest()
var (project, mergeRequest) = context.CreateMergeRequest();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
var mergeRequestChanges = mergeRequestClient.Changes(mergeRequest.Iid);
- var changes = mergeRequestChanges.MergeRequestChange.Changes;
+
+ var changes = await GitLabTestContext.RetryUntilAsync(
+ () => mergeRequestChanges.MergeRequestChange.Changes,
+ changes => changes.Any(),
+ TimeSpan.FromSeconds(10));
+
Assert.AreEqual(1, changes.Length);
Assert.AreEqual(100644, changes[0].AMode);
Assert.AreEqual(100644, changes[0].BMode);
diff --git a/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs b/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs
index 3a25c236..859fb59b 100644
--- a/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs
+++ b/NGitLab.Tests/MergeRequest/MergeRequestClientTests.cs
@@ -2,7 +2,6 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
-using Meziantou.Framework.Versioning;
using NGitLab.Models;
using NGitLab.Tests.Docker;
using NuGet.Versioning;
@@ -26,6 +25,7 @@ public async Task Test_merge_request_api()
ListMergeRequest(mergeRequestClient, mergeRequest);
mergeRequest = UpdateMergeRequest(mergeRequestClient, mergeRequest);
+ Test_can_update_labels_with_delta(mergeRequestClient, mergeRequest);
Test_can_update_a_subset_of_merge_request_fields(mergeRequestClient, mergeRequest);
await GitLabTestContext.RetryUntilAsync(
@@ -49,6 +49,11 @@ await GitLabTestContext.RetryUntilAsync(
// .ConfigureAwait(false);
var commits = mergeRequestClient.Commits(mergeRequest.Iid).All;
Assert.IsTrue(commits.Any(), "Can return the commits");
+
+ if (context.IsGitLabVersionInRange(VersionRange.Parse("[15.6,)"), out _))
+ Assert.IsNotNull(mergeRequest.DetailedMergeStatus.EnumValue);
+ else
+ Assert.IsNull(mergeRequest.DetailedMergeStatus.EnumValue);
}
[Test]
@@ -83,7 +88,12 @@ public async Task Test_merge_request_rebase()
"There should be a 1-commit divergence between the default branch NOW and its state at the moment the MR was created");
RebaseMergeRequest(mergeRequestClient, mergeRequest);
- var commits = mergeRequestClient.Commits(mergeRequest.Iid).All;
+
+ var commits = await GitLabTestContext.RetryUntilAsync(
+ () => mergeRequestClient.Commits(mergeRequest.Iid).All,
+ commits => commits.Any(),
+ TimeSpan.FromSeconds(10));
+
Assert.IsTrue(commits.Any(), "Can return the commits");
}
@@ -121,7 +131,11 @@ public async Task Test_merge_request_rebaseasync_skip_ci()
var rebaseResult = await mergeRequestClient.RebaseAsync(mergeRequest.Iid, new MergeRequestRebase { SkipCi = true });
Assert.IsTrue(rebaseResult.RebaseInProgress);
- var commits = mergeRequestClient.Commits(mergeRequest.Iid).All;
+ var commits = await GitLabTestContext.RetryUntilAsync(
+ () => mergeRequestClient.Commits(mergeRequest.Iid).All,
+ commits => commits.Any(),
+ TimeSpan.FromSeconds(10));
+
Assert.IsTrue(commits.Any(), "Can return the commits");
}
@@ -178,7 +192,7 @@ public async Task Test_merge_request_approvers()
using var context = await GitLabTestContext.CreateAsync();
// https://about.gitlab.com/releases/2021/04/22/gitlab-13-11-released/#removal-of-merge-request-approvers-endpoint-in-favor-of-approval-rules-api
- context.ReportTestAsInconclusiveIfVersionOutOfRange(VersionRange.Parse("[,13.11)"));
+ context.ReportTestAsInconclusiveIfGitLabVersionOutOfRange(VersionRange.Parse("[,13.11)"));
var (project, mergeRequest) = context.CreateMergeRequest();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
@@ -276,7 +290,11 @@ public async Task Test_merge_request_versions()
var (project, mergeRequest) = context.CreateMergeRequest();
var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
- var versions = mergeRequestClient.GetVersionsAsync(mergeRequest.Iid);
+ var versions = await GitLabTestContext.RetryUntilAsync(
+ () => mergeRequestClient.GetVersionsAsync(mergeRequest.Iid),
+ versions => versions.Any(),
+ TimeSpan.FromSeconds(10));
+
var version = versions.First();
Assert.AreEqual(mergeRequest.Sha, version.HeadCommitSha);
@@ -335,6 +353,20 @@ private static void Test_can_update_a_subset_of_merge_request_fields(IMergeReque
Assert.AreEqual(mergeRequest.Description, updated.Description);
}
+ private static void Test_can_update_labels_with_delta(IMergeRequestClient mergeRequestClient, MergeRequest mergeRequest)
+ {
+ // Ensure original labels are "a,b"
+ CollectionAssert.AreEqual(new[] { "a", "b" }, mergeRequest.Labels);
+
+ var updated = mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate
+ {
+ RemoveLabels = "b",
+ AddLabels = "c,d",
+ });
+
+ CollectionAssert.AreEqual(new[] { "a", "c", "d" }, updated.Labels);
+ }
+
public static void AcceptMergeRequest(IMergeRequestClient mergeRequestClient, MergeRequest request)
{
Policy
diff --git a/NGitLab.Tests/MergeRequest/MergeRequestDiscussionsClientTests.cs b/NGitLab.Tests/MergeRequest/MergeRequestDiscussionsClientTests.cs
index fca5277a..bb4200ce 100644
--- a/NGitLab.Tests/MergeRequest/MergeRequestDiscussionsClientTests.cs
+++ b/NGitLab.Tests/MergeRequest/MergeRequestDiscussionsClientTests.cs
@@ -34,6 +34,26 @@ public async Task AddDiscussionToMergeRequest_DiscussionCreated()
CollectionAssert.IsNotEmpty(discussions);
}
+ [Test]
+ [NGitLabRetry]
+ public async Task GetDiscussion_DiscussionFound()
+ {
+ using var context = await GitLabTestContext.CreateAsync();
+ var (project, mergeRequest) = context.CreateMergeRequest();
+ var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
+ var mergeRequestDiscussions = mergeRequestClient.Discussions(mergeRequest.Iid);
+
+ const string discussionMessage = "Discussion for MR";
+ var newDiscussion = new MergeRequestDiscussionCreate
+ {
+ Body = discussionMessage,
+ };
+ var discussion = mergeRequestDiscussions.Add(newDiscussion);
+
+ var gotDiscussion = await mergeRequestDiscussions.GetAsync(discussion.Id);
+ Assert.NotNull(gotDiscussion);
+ }
+
[Test]
[NGitLabRetry]
public async Task EditCommentFromDiscussion_CommentEdited()
diff --git a/NGitLab.Tests/Milestone/MilestoneClientTests.cs b/NGitLab.Tests/Milestone/MilestoneClientTests.cs
index 24ad7444..b5cc7e5a 100644
--- a/NGitLab.Tests/Milestone/MilestoneClientTests.cs
+++ b/NGitLab.Tests/Milestone/MilestoneClientTests.cs
@@ -63,6 +63,41 @@ public async Task Test_group_milestone_api()
DeleteMilestone(context, MilestoneScope.Groups, group.Id, milestone);
}
+ [Test]
+ [NGitLabRetry]
+ public async Task Test_project_milestone_merge_requests()
+ {
+ using var context = await GitLabTestContext.CreateAsync();
+ var (project, mergeRequest) = context.CreateMergeRequest();
+
+ var milestoneClient = context.Client.GetMilestone(project.Id);
+ var milestone = CreateMilestone(context, MilestoneScope.Projects, project.Id, "my-super-milestone");
+
+ var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
+ mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate { MilestoneId = milestone.Id });
+
+ var mergeRequests = milestoneClient.GetMergeRequests(milestone.Id).ToArray();
+ Assert.AreEqual(1, mergeRequests.Length, "The query retrieved all merged requests that assigned to the milestone.");
+ }
+
+ [Test]
+ [NGitLabRetry]
+ public async Task Test_group_milestone_merge_requests()
+ {
+ using var context = await GitLabTestContext.CreateAsync();
+ var group = context.CreateGroup();
+ var (project, mergeRequest) = context.CreateMergeRequest(configureProject: project => project.NamespaceId = group.Id.ToString());
+
+ var milestoneClient = context.Client.GetGroupMilestone(group.Id);
+ var milestone = CreateMilestone(context, MilestoneScope.Groups, group.Id, "my-super-milestone");
+
+ var mergeRequestClient = context.Client.GetMergeRequest(project.Id);
+ mergeRequestClient.Update(mergeRequest.Iid, new MergeRequestUpdate { MilestoneId = milestone.Id });
+
+ var mergeRequests = milestoneClient.GetMergeRequests(milestone.Id).ToArray();
+ Assert.AreEqual(1, mergeRequests.Length, "The query retrieved all merged requests that assigned to the milestone.");
+ }
+
private static Models.Milestone CreateMilestone(GitLabTestContext context, MilestoneScope scope, int id, string title)
{
var milestoneClient = scope == MilestoneScope.Projects ? context.Client.GetMilestone(id) : context.Client.GetGroupMilestone(id);
@@ -79,6 +114,8 @@ private static Models.Milestone CreateMilestone(GitLabTestContext context, Miles
Assert.That(milestone.Description, Is.EqualTo($"{title} description"));
Assert.That(milestone.StartDate, Is.EqualTo("2017-08-20"));
Assert.That(milestone.DueDate, Is.EqualTo("2017-09-20"));
+ Assert.That(milestone.ProjectId, scope == MilestoneScope.Projects ? Is.EqualTo(id) : Is.Null);
+ Assert.That(milestone.GroupId, scope == MilestoneScope.Groups ? Is.EqualTo(id) : Is.Null);
return milestone;
}
@@ -100,6 +137,8 @@ private static Models.Milestone UpdateMilestone(GitLabTestContext context, Miles
Assert.That(updatedMilestone.StartDate, Is.EqualTo("2018-08-20"));
Assert.That(updatedMilestone.DueDate, Is.EqualTo("2018-09-20"));
Assert.That(updatedMilestone.State, Is.EqualTo(milestone.State));
+ Assert.That(milestone.ProjectId, scope == MilestoneScope.Projects ? Is.EqualTo(id) : Is.Null);
+ Assert.That(milestone.GroupId, scope == MilestoneScope.Groups ? Is.EqualTo(id) : Is.Null);
return updatedMilestone;
}
@@ -118,6 +157,8 @@ private static Models.Milestone UpdatePartialMilestone(GitLabTestContext context
Assert.That(updatedMilestone.StartDate, Is.EqualTo(milestone.StartDate));
Assert.That(updatedMilestone.DueDate, Is.EqualTo(milestone.DueDate));
Assert.That(updatedMilestone.State, Is.EqualTo(milestone.State));
+ Assert.That(updatedMilestone.ProjectId, scope == MilestoneScope.Projects ? Is.EqualTo(id) : Is.Null);
+ Assert.That(updatedMilestone.GroupId, scope == MilestoneScope.Groups ? Is.EqualTo(id) : Is.Null);
return updatedMilestone;
}
@@ -125,15 +166,17 @@ private static Models.Milestone UpdatePartialMilestone(GitLabTestContext context
private static Models.Milestone ActivateMilestone(GitLabTestContext context, MilestoneScope scope, int id, Models.Milestone milestone)
{
var milestoneClient = scope == MilestoneScope.Projects ? context.Client.GetMilestone(id) : context.Client.GetGroupMilestone(id);
- var closedMilestone = milestoneClient.Activate(milestone.Id);
+ var activeMilestone = milestoneClient.Activate(milestone.Id);
- Assert.That(closedMilestone.State, Is.EqualTo(nameof(MilestoneState.active)));
- Assert.That(closedMilestone.Title, Is.EqualTo(milestone.Title));
- Assert.That(closedMilestone.Description, Is.EqualTo(milestone.Description));
- Assert.That(closedMilestone.StartDate, Is.EqualTo(milestone.StartDate));
- Assert.That(closedMilestone.DueDate, Is.EqualTo(milestone.DueDate));
+ Assert.That(activeMilestone.State, Is.EqualTo(nameof(MilestoneState.active)));
+ Assert.That(activeMilestone.Title, Is.EqualTo(milestone.Title));
+ Assert.That(activeMilestone.Description, Is.EqualTo(milestone.Description));
+ Assert.That(activeMilestone.StartDate, Is.EqualTo(milestone.StartDate));
+ Assert.That(activeMilestone.DueDate, Is.EqualTo(milestone.DueDate));
+ Assert.That(activeMilestone.ProjectId, scope == MilestoneScope.Projects ? Is.EqualTo(id) : Is.Null);
+ Assert.That(activeMilestone.GroupId, scope == MilestoneScope.Groups ? Is.EqualTo(id) : Is.Null);
- return closedMilestone;
+ return activeMilestone;
}
private static Models.Milestone CloseMilestone(GitLabTestContext context, MilestoneScope scope, int id, Models.Milestone milestone)
@@ -146,6 +189,8 @@ private static Models.Milestone CloseMilestone(GitLabTestContext context, Milest
Assert.That(closedMilestone.Description, Is.EqualTo(milestone.Description));
Assert.That(closedMilestone.StartDate, Is.EqualTo(milestone.StartDate));
Assert.That(closedMilestone.DueDate, Is.EqualTo(milestone.DueDate));
+ Assert.That(closedMilestone.ProjectId, scope == MilestoneScope.Projects ? Is.EqualTo(id) : Is.Null);
+ Assert.That(closedMilestone.GroupId, scope == MilestoneScope.Groups ? Is.EqualTo(id) : Is.Null);
return closedMilestone;
}
diff --git a/NGitLab.Tests/NGitLab.Tests.csproj b/NGitLab.Tests/NGitLab.Tests.csproj
index 73cb1933..bab4a6bb 100644
--- a/NGitLab.Tests/NGitLab.Tests.csproj
+++ b/NGitLab.Tests/NGitLab.Tests.csproj
@@ -14,14 +14,14 @@
-
-
-
+
+
+
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/NGitLab.Tests/PipelineTests.cs b/NGitLab.Tests/PipelineTests.cs
index bdf2ff38..80c004f9 100644
--- a/NGitLab.Tests/PipelineTests.cs
+++ b/NGitLab.Tests/PipelineTests.cs
@@ -200,13 +200,40 @@ public async Task Test_get_triggered_pipeline_variables()
var trigger = triggers.Create("Test Trigger");
var ciJobToken = trigger.Token;
- var pipeline = pipelineClient.CreatePipelineWithTrigger(ciJobToken, project.DefaultBranch, new Dictionary(StringComparer.InvariantCulture) { { "Test", "HelloWorld" } });
+ var pipeline = pipelineClient.CreatePipelineWithTrigger(ciJobToken, project.DefaultBranch, new Dictionary(StringComparer.Ordinal) { { "Test", "HelloWorld" } });
var variables = pipelineClient.GetVariables(pipeline.Id);
Assert.IsTrue(variables.Any(v =>
- v.Key.Equals("Test", StringComparison.InvariantCulture) &&
- v.Value.Equals("HelloWorld", StringComparison.InvariantCulture)));
+ v.Key.Equals("Test", StringComparison.Ordinal) &&
+ v.Value.Equals("HelloWorld", StringComparison.Ordinal)));
+ }
+
+ [Test]
+ [NGitLabRetry]
+ public async Task Test_retry()
+ {
+ using var context = await GitLabTestContext.CreateAsync();
+ var project = context.CreateProject();
+ var pipelineClient = context.Client.GetPipelines(project.Id);
+
+ using (await context.StartRunnerForOneJobAsync(project.Id))
+ {
+ JobTests.AddGitLabCiFile(context.Client, project, pipelineSucceeds: false);
+ var pipeline = await GitLabTestContext.RetryUntilAsync(() => pipelineClient.All.FirstOrDefault(), pipeline =>
+ {
+ if (pipeline != null)
+ {
+ TestContext.WriteLine("Pipeline status: " + pipeline.Status);
+ return pipeline.Status is JobStatus.Failed;
+ }
+
+ return false;
+ }, TimeSpan.FromMinutes(2));
+
+ var retriedPipeline = await pipelineClient.RetryAsync(pipeline.Id);
+ Assert.AreNotEqual(JobStatus.Failed, retriedPipeline.Status); // Should be created or running
+ }
}
}
}
diff --git a/NGitLab.Tests/ProjectLevelApprovalRulesClientTests.cs b/NGitLab.Tests/ProjectLevelApprovalRulesClientTests.cs
index 3383c59a..846c3031 100644
--- a/NGitLab.Tests/ProjectLevelApprovalRulesClientTests.cs
+++ b/NGitLab.Tests/ProjectLevelApprovalRulesClientTests.cs
@@ -16,7 +16,7 @@ public class ProjectLevelApprovalRulesClientTests
public async Task SetUp()
{
context = await GitLabTestContext.CreateAsync();
- context.ReportTestAsInconclusiveIfVersionOutOfRange(SupportedVersionRange);
+ context.ReportTestAsInconclusiveIfGitLabVersionOutOfRange(SupportedVersionRange);
}
[TearDown]
diff --git a/NGitLab.Tests/ProtectedBranchTests.cs b/NGitLab.Tests/ProtectedBranchTests.cs
index eb8055c8..260d72f9 100644
--- a/NGitLab.Tests/ProtectedBranchTests.cs
+++ b/NGitLab.Tests/ProtectedBranchTests.cs
@@ -33,7 +33,7 @@ public async Task ProtectBranch_Test()
{
new AccessLevelInfo
{
- AccessLevel = AccessLevel.NoAccess,
+ AccessLevel = AccessLevel.Admin,
Description = "Example",
},
},
diff --git a/NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs b/NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs
index 125adc57..0f756ccb 100644
--- a/NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs
+++ b/NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs
@@ -130,6 +130,102 @@ public async Task GetCommitBySha1Range()
Assert.AreEqual(allCommits[3].Id, commits[1].Id);
}
+ [Test]
+ [NGitLabRetry]
+ public async Task GetCommitsSince()
+ {
+ // Arrange
+ using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 5);
+
+ var defaultBranch = context.Project.DefaultBranch;
+ var since = DateTime.UtcNow;
+ var expectedSinceValue = Uri.EscapeDataString(since.ToString("s", CultureInfo.InvariantCulture));
+ var commitRequest = new GetCommitsRequest
+ {
+ RefName = defaultBranch,
+ Since = since,
+ };
+
+ // Act
+ var commits = context.RepositoryClient.GetCommits(commitRequest).ToArray();
+
+ // Assert
+ var lastRequestQueryString = context.Context.LastRequest.RequestUri.Query;
+
+ Assert.True(lastRequestQueryString.Contains($"since={expectedSinceValue}"));
+ }
+
+ [Test]
+ [NGitLabRetry]
+ public async Task GetCommitsDoesntIncludeSinceWhenNotSpecified()
+ {
+ // Arrange
+ using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 5);
+
+ var defaultBranch = context.Project.DefaultBranch;
+ var commitRequest = new GetCommitsRequest
+ {
+ RefName = defaultBranch,
+ Since = null,
+ };
+
+ // Act
+ var commits = context.RepositoryClient.GetCommits(commitRequest).ToArray();
+
+ // Assert
+ var lastRequestQueryString = context.Context.LastRequest.RequestUri.Query;
+
+ Assert.False(lastRequestQueryString.Contains("since="));
+ }
+
+ [Test]
+ [NGitLabRetry]
+ public async Task GetCommitsUntil()
+ {
+ // Arrange
+ using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 5);
+
+ var defaultBranch = context.Project.DefaultBranch;
+ var until = DateTime.UtcNow;
+ var expectedUntilValue = Uri.EscapeDataString(until.ToString("s", CultureInfo.InvariantCulture));
+ var commitRequest = new GetCommitsRequest
+ {
+ RefName = defaultBranch,
+ Until = until,
+ };
+
+ // Act
+ var commits = context.RepositoryClient.GetCommits(commitRequest).ToArray();
+
+ // Assert
+ var lastRequestQueryString = context.Context.LastRequest.RequestUri.Query;
+
+ Assert.True(lastRequestQueryString.Contains($"until={expectedUntilValue}"));
+ }
+
+ [Test]
+ [NGitLabRetry]
+ public async Task GetCommitsDoesntIncludeUntilWhenNotSpecified()
+ {
+ // Arrange
+ using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 5);
+
+ var defaultBranch = context.Project.DefaultBranch;
+ var commitRequest = new GetCommitsRequest
+ {
+ RefName = defaultBranch,
+ Until = null,
+ };
+
+ // Act
+ var commits = context.RepositoryClient.GetCommits(commitRequest).ToArray();
+
+ // Assert
+ var lastRequestQueryString = context.Context.LastRequest.RequestUri.Query;
+
+ Assert.False(lastRequestQueryString.Contains("until="));
+ }
+
[Test]
[NGitLabRetry]
public async Task GetCommitDiff()
diff --git a/NGitLab/GetCommitsRequest.cs b/NGitLab/GetCommitsRequest.cs
index 3f3a9a75..ec24a860 100644
--- a/NGitLab/GetCommitsRequest.cs
+++ b/NGitLab/GetCommitsRequest.cs
@@ -1,4 +1,6 @@
-namespace NGitLab
+using System;
+
+namespace NGitLab
{
public class GetCommitsRequest
{
@@ -13,5 +15,9 @@ public class GetCommitsRequest
public int MaxResults { get; set; }
public uint PerPage { get; set; } = DefaultPerPage;
+
+ public DateTime? Since { get; set; }
+
+ public DateTime? Until { get; set; }
}
}
diff --git a/NGitLab/IIssueClient.cs b/NGitLab/IIssueClient.cs
index 12e23ce9..a5145f6b 100644
--- a/NGitLab/IIssueClient.cs
+++ b/NGitLab/IIssueClient.cs
@@ -134,6 +134,23 @@ public interface IIssueClient
/// The list of MR that are related this issue.
IEnumerable RelatedTo(int projectId, int issueIid);
+ ///
+ /// Get all Issues that are linked to a particular issue of particular project.
+ ///
+ /// The project id.
+ /// The id of the issue in the project's scope.
+ /// The list of Issues linked to this issue.
+ GitLabCollectionResponse LinkedToAsync(int projectId, int issueId);
+
+ ///
+ /// Create links between Issues.
+ ///
+ /// The project id.
+ /// The id of the issue in the project's scope.
+ /// The target project id.
+ /// The target id of the issue to link to.
+ bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId);
+
GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid);
///
diff --git a/NGitLab/IMergeRequestDiscussionClient.cs b/NGitLab/IMergeRequestDiscussionClient.cs
index 65c9d3f9..0b735a49 100644
--- a/NGitLab/IMergeRequestDiscussionClient.cs
+++ b/NGitLab/IMergeRequestDiscussionClient.cs
@@ -1,4 +1,6 @@
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using NGitLab.Models;
namespace NGitLab
@@ -7,6 +9,10 @@ public interface IMergeRequestDiscussionClient
{
IEnumerable All { get; }
+ MergeRequestDiscussion Get(string id);
+
+ Task GetAsync(string id, CancellationToken cancellationToken = default);
+
MergeRequestDiscussion Add(MergeRequestDiscussionCreate discussion);
MergeRequestDiscussion Resolve(MergeRequestDiscussionResolve resolve);
diff --git a/NGitLab/IMilestoneClient.cs b/NGitLab/IMilestoneClient.cs
index fbeec980..d4fe4fbf 100644
--- a/NGitLab/IMilestoneClient.cs
+++ b/NGitLab/IMilestoneClient.cs
@@ -25,5 +25,7 @@ public interface IMilestoneClient
Milestone Close(int milestoneId);
Milestone Activate(int milestoneId);
+
+ IEnumerable GetMergeRequests(int milestoneId);
}
}
diff --git a/NGitLab/IPipelineClient.cs b/NGitLab/IPipelineClient.cs
index 151d0ee5..5240b9d0 100644
--- a/NGitLab/IPipelineClient.cs
+++ b/NGitLab/IPipelineClient.cs
@@ -115,5 +115,7 @@ public interface IPipelineClient
///
///
GitLabCollectionResponse GetBridgesAsync(PipelineBridgeQuery query);
+
+ Task RetryAsync(int pipelineId, CancellationToken cancellationToken = default);
}
}
diff --git a/NGitLab/Impl/ContributorClient.cs b/NGitLab/Impl/ContributorClient.cs
index 0f88f056..56e6871c 100644
--- a/NGitLab/Impl/ContributorClient.cs
+++ b/NGitLab/Impl/ContributorClient.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using NGitLab.Extensions;
+using System;
+using System.Collections.Generic;
using NGitLab.Models;
namespace NGitLab.Impl
@@ -8,18 +8,22 @@ internal sealed class ContributorClient : IContributorClient
{
private readonly API _api;
private readonly string _contributorPath;
- private readonly int _projectId;
- public ContributorClient(API api, string repoPath, int projectId)
+ public ContributorClient(API api, string repoPath)
{
_api = api;
_contributorPath = repoPath + Contributor.Url;
- _projectId = projectId;
+ }
+
+ [Obsolete("Argument projectId is redundant, please use ContributorClient(API api, string repoPath) instead.")]
+ public ContributorClient(API api, string repoPath, int projectId)
+ : this(api, repoPath)
+ {
}
///
/// HACK: We force the order_by and sort due to a pagination bug from GitLab
///
- public IEnumerable All => _api.Get().GetAll(_contributorPath + $"?id={_projectId.ToStringInvariant()}&order_by=commits&sort=desc");
+ public IEnumerable All => _api.Get().GetAll(_contributorPath + $"?order_by=commits&sort=desc");
}
}
diff --git a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
index 850980f3..54972426 100644
--- a/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
+++ b/NGitLab/Impl/HttpRequestor.GitLabRequest.cs
@@ -48,7 +48,11 @@ public GitLabRequest(Uri url, MethodType method, object data, string apiToken, R
if (apiToken != null)
{
- Headers.Add("Private-Token", apiToken);
+ // Use the 'Authorization: Bearer token' header as this provides flexibility to use
+ // personal, project, group and OAuth tokens. The 'PRIVATE-TOKEN' header does not
+ // provide OAuth token support.
+ // Reference: https://docs.gitlab.com/ee/api/rest/#personalprojectgroup-access-tokens
+ Headers.Add(HttpRequestHeader.Authorization, string.Concat("Bearer ", apiToken));
}
if (!string.IsNullOrEmpty(options?.UserAgent))
diff --git a/NGitLab/Impl/IssueClient.cs b/NGitLab/Impl/IssueClient.cs
index 4a1bb103..7835988e 100644
--- a/NGitLab/Impl/IssueClient.cs
+++ b/NGitLab/Impl/IssueClient.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,8 @@ public class IssueClient : IIssueClient
{
private const string IssuesUrl = "/issues";
private const string IssueByIdUrl = "/issues/{0}";
+ private const string LinkedIssuesByIdUrl = "/projects/{0}/issues/{1}/links";
+ private const string CreateLinkBetweenIssuesUrl = "/projects/{0}/issues/{1}/links?target_project_id={2}&target_issue_iid={3}";
private const string GroupIssuesUrl = "/groups/{0}/issues";
private const string ProjectIssuesUrl = "/projects/{0}/issues";
private const string SingleIssueUrl = "/projects/{0}/issues/{1}";
@@ -145,6 +148,25 @@ public IEnumerable RelatedTo(int projectId, int issueIid)
return _api.Get().GetAll(string.Format(CultureInfo.InvariantCulture, RelatedToUrl, projectId, issueIid));
}
+ public GitLabCollectionResponse LinkedToAsync(int projectId, int issueId)
+ {
+ return _api.Get().GetAllAsync(string.Format(CultureInfo.InvariantCulture, LinkedIssuesByIdUrl, projectId, issueId));
+ }
+
+ public bool CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId,
+ int targetIssueId)
+ {
+ try
+ {
+ _api.Post().Execute(string.Format(CultureInfo.InvariantCulture, CreateLinkBetweenIssuesUrl, sourceProjectId, sourceIssueId, targetProjectId, targetIssueId));
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
public GitLabCollectionResponse RelatedToAsync(int projectId, int issueIid)
{
return _api.Get().GetAllAsync(string.Format(CultureInfo.InvariantCulture, RelatedToUrl, projectId, issueIid));
diff --git a/NGitLab/Impl/MergeRequestDiscussionClient.cs b/NGitLab/Impl/MergeRequestDiscussionClient.cs
index 48db25df..7f0d457d 100644
--- a/NGitLab/Impl/MergeRequestDiscussionClient.cs
+++ b/NGitLab/Impl/MergeRequestDiscussionClient.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
using NGitLab.Models;
namespace NGitLab.Impl
@@ -17,6 +19,10 @@ public MergeRequestDiscussionClient(API api, string projectPath, int mergeReques
public IEnumerable All => _api.Get().GetAll(_discussionsPath);
+ public MergeRequestDiscussion Get(string id) => _api.Get().To($"{_discussionsPath}/{id}");
+
+ public Task GetAsync(string id, CancellationToken cancellationToken = default) => _api.Get().ToAsync($"{_discussionsPath}/{id}", cancellationToken);
+
public MergeRequestDiscussion Add(MergeRequestDiscussionCreate comment) => _api.Post().With(comment).To(_discussionsPath);
public MergeRequestDiscussion Resolve(MergeRequestDiscussionResolve resolve) => _api.Put().With(resolve).To(_discussionsPath + "/" + resolve.Id);
diff --git a/NGitLab/Impl/MilestoneClient.cs b/NGitLab/Impl/MilestoneClient.cs
index 22387e36..5e0c00e6 100644
--- a/NGitLab/Impl/MilestoneClient.cs
+++ b/NGitLab/Impl/MilestoneClient.cs
@@ -57,6 +57,9 @@ public Milestone Activate(int milestoneId) => _api
.Put().With(new MilestoneUpdateState { NewState = nameof(MilestoneStateEvent.activate) })
.To($"{_milestonePath}/{milestoneId.ToStringInvariant()}");
+ public IEnumerable GetMergeRequests(int milestoneId) => _api
+ .Get().GetAll($"{_milestonePath}/{milestoneId.ToStringInvariant()}/merge_requests");
+
public void Delete(int milestoneId) => _api
.Delete()
.Execute($"{_milestonePath}/{milestoneId.ToStringInvariant()}");
diff --git a/NGitLab/Impl/PipelineClient.cs b/NGitLab/Impl/PipelineClient.cs
index aa9743dc..6f9305ac 100644
--- a/NGitLab/Impl/PipelineClient.cs
+++ b/NGitLab/Impl/PipelineClient.cs
@@ -209,5 +209,11 @@ private string CreateGetBridgesUrl(PipelineBridgeQuery query)
url = Utils.AddParameter(url, "scope", query.Scope);
return url;
}
+
+ public Task RetryAsync(int pipelineId, CancellationToken cancellationToken = default)
+ {
+ var url = $"{_pipelinesPath}/{pipelineId.ToStringInvariant()}/retry";
+ return _api.Post().ToAsync(url, cancellationToken);
+ }
}
}
diff --git a/NGitLab/Impl/RepositoryClient.cs b/NGitLab/Impl/RepositoryClient.cs
index 27abfc2f..56a0c0ea 100644
--- a/NGitLab/Impl/RepositoryClient.cs
+++ b/NGitLab/Impl/RepositoryClient.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using NGitLab.Extensions;
@@ -24,7 +25,7 @@ public RepositoryClient(API api, int projectId)
public ITagClient Tags => new TagClient(_api, _repoPath);
- public IContributorClient Contributors => new ContributorClient(_api, _repoPath, _projectId);
+ public IContributorClient Contributors => new ContributorClient(_api, _repoPath);
public IEnumerable Tree => _api.Get().GetAll(_repoPath + "/tree");
@@ -85,6 +86,16 @@ public IEnumerable GetCommits(GetCommitsRequest request)
lst.Add($"first_parent={Uri.EscapeDataString(request.FirstParent.ToString())}");
}
+ if (request.Since.HasValue)
+ {
+ lst.Add($"since={Uri.EscapeDataString(request.Since.Value.ToString("s", CultureInfo.InvariantCulture))}");
+ }
+
+ if (request.Until.HasValue)
+ {
+ lst.Add($"until={Uri.EscapeDataString(request.Until.Value.ToString("s", CultureInfo.InvariantCulture))}");
+ }
+
var perPage = request.MaxResults > 0 ? Math.Min(request.MaxResults, request.PerPage) : request.PerPage;
lst.Add($"per_page={perPage.ToStringInvariant()}");
diff --git a/NGitLab/Models/DetailedMergeStatus.cs b/NGitLab/Models/DetailedMergeStatus.cs
new file mode 100644
index 00000000..54f3504e
--- /dev/null
+++ b/NGitLab/Models/DetailedMergeStatus.cs
@@ -0,0 +1,36 @@
+using System.Runtime.Serialization;
+
+namespace NGitLab.Models;
+
+///
+/// Values that represent the 'detailed_merge_status' potential values.
+///
+public enum DetailedMergeStatus
+{
+ [EnumMember(Value = "blocked_status")]
+ BlockedStatus,
+ [EnumMember(Value = "broken_status")]
+ BrokenStatus,
+ [EnumMember(Value = "checking")]
+ Checking,
+ [EnumMember(Value = "unchecked")]
+ Unchecked,
+ [EnumMember(Value = "ci_must_pass")]
+ CiMustPass,
+ [EnumMember(Value = "ci_still_running")]
+ CiStillRunning,
+ [EnumMember(Value = "discussions_not_resolved")]
+ DiscussionsNotResolved,
+ [EnumMember(Value = "draft_status")]
+ DraftStatus,
+ [EnumMember(Value = "external_status_checks")]
+ ExternalStatusChecks,
+ [EnumMember(Value = "mergeable")]
+ Mergeable,
+ [EnumMember(Value = "not_approved")]
+ NotApproved,
+ [EnumMember(Value = "not_open")]
+ NotOpen,
+ [EnumMember(Value = "policies_denied")]
+ PoliciesDenied,
+}
diff --git a/NGitLab/Models/MergeRequest.cs b/NGitLab/Models/MergeRequest.cs
index 5700a68e..ff8095f8 100644
--- a/NGitLab/Models/MergeRequest.cs
+++ b/NGitLab/Models/MergeRequest.cs
@@ -2,144 +2,143 @@
using System.Text.Json.Serialization;
using NGitLab.Extensions;
-namespace NGitLab.Models
+namespace NGitLab.Models;
+
+public class MergeRequest
{
- public class MergeRequest
- {
- public const string Url = "/merge_requests";
+ public const string Url = "/merge_requests";
+
+ [JsonPropertyName("id")]
+ public int Id;
- [JsonPropertyName("id")]
- public int Id;
+ [JsonPropertyName("iid")]
+ public int Iid;
- [JsonPropertyName("iid")]
- public int Iid;
+ [JsonPropertyName("state")]
+ public string State;
- [JsonPropertyName("state")]
- public string State;
+ [JsonPropertyName("title")]
+ public string Title;
- [JsonPropertyName("title")]
- public string Title;
+ [JsonPropertyName("assignee")]
+ public User Assignee;
- [JsonPropertyName("assignee")]
- public User Assignee;
+ [JsonPropertyName("author")]
+ public User Author;
- [JsonPropertyName("author")]
- public User Author;
+ [JsonPropertyName("created_at")]
+ public DateTime CreatedAt;
- [JsonPropertyName("created_at")]
- public DateTime CreatedAt;
+ [JsonPropertyName("description")]
+ public string Description;
- [JsonPropertyName("description")]
- public string Description;
+ [JsonPropertyName("downvotes")]
+ public int Downvotes;
- [JsonPropertyName("downvotes")]
- public int Downvotes;
+ [JsonPropertyName("upvotes")]
+ public int Upvotes;
- [JsonPropertyName("upvotes")]
- public int Upvotes;
+ [JsonPropertyName("updated_at")]
+ public DateTime UpdatedAt;
- [JsonPropertyName("updated_at")]
- public DateTime UpdatedAt;
+ [JsonPropertyName("target_branch")]
+ public string TargetBranch;
- [JsonPropertyName("target_branch")]
- public string TargetBranch;
+ [JsonPropertyName("source_branch")]
+ public string SourceBranch;
- [JsonPropertyName("source_branch")]
- public string SourceBranch;
+ [JsonPropertyName("project_id")]
+ public int ProjectId;
- [JsonPropertyName("project_id")]
- public int ProjectId;
+ [JsonPropertyName("source_project_id")]
+ public int SourceProjectId;
- [JsonPropertyName("source_project_id")]
- public int SourceProjectId;
+ [JsonPropertyName("target_project_id")]
+ public int TargetProjectId;
- [JsonPropertyName("target_project_id")]
- public int TargetProjectId;
+ [JsonPropertyName("work_in_progress")]
+ public bool? WorkInProgress;
- [JsonPropertyName("work_in_progress")]
- public bool? WorkInProgress;
+ [JsonPropertyName("milestone")]
+ public Milestone Milestone;
- [JsonPropertyName("milestone")]
- public Milestone Milestone;
+ [JsonPropertyName("labels")]
+ public string[] Labels;
- [JsonPropertyName("labels")]
- public string[] Labels;
+ [JsonPropertyName("merge_when_pipeline_succeeds")]
+ public bool MergeWhenPipelineSucceeds;
- [JsonPropertyName("merge_when_pipeline_succeeds")]
- public bool MergeWhenPipelineSucceeds;
+ [JsonPropertyName("merge_status")]
+ public string MergeStatus;
- [JsonPropertyName("merge_status")]
- public string MergeStatus;
+ [JsonPropertyName("sha")]
+ public string Sha;
- [JsonPropertyName("sha")]
- public string Sha;
+ [JsonPropertyName("merge_commit_sha")]
+ public string MergeCommitSha;
- [JsonPropertyName("merge_commit_sha")]
- public string MergeCommitSha;
+ [JsonPropertyName("squash_commit_sha")]
+ public string SquashCommitSha;
- [JsonPropertyName("squash_commit_sha")]
- public string SquashCommitSha;
+ [JsonPropertyName("diff_refs")]
+ public DiffRefs DiffRefs;
- [JsonPropertyName("diff_refs")]
- public DiffRefs DiffRefs;
+ [JsonPropertyName("should_remove_source_branch")]
+ public bool? ShouldRemoveSourceBranch;
- [JsonPropertyName("should_remove_source_branch")]
- public bool? ShouldRemoveSourceBranch;
+ [JsonPropertyName("force_remove_source_branch")]
+ public bool ForceRemoveSourceBranch;
- [JsonPropertyName("force_remove_source_branch")]
- public bool ForceRemoveSourceBranch;
+ [JsonPropertyName("squash")]
+ public bool Squash;
- [JsonPropertyName("squash")]
- public bool Squash;
+ [JsonPropertyName("changes_count")]
+ public string ChangesCount;
- [JsonPropertyName("changes_count")]
- public string ChangesCount;
+ [JsonPropertyName("web_url")]
+ public string WebUrl;
- [JsonPropertyName("web_url")]
- public string WebUrl;
+ [JsonPropertyName("merged_by")]
+ public User MergedBy;
- [JsonPropertyName("merged_by")]
- public User MergedBy;
+ [JsonPropertyName("merged_at")]
+ public DateTime? MergedAt;
- [JsonPropertyName("merged_at")]
- public DateTime? MergedAt;
+ [JsonPropertyName("closed_at")]
+ public DateTime? ClosedAt;
- [JsonPropertyName("closed_at")]
- public DateTime? ClosedAt;
+ [JsonPropertyName("closed_by")]
+ public User ClosedBy;
- [JsonPropertyName("closed_by")]
- public User ClosedBy;
+ [JsonPropertyName("assignees")]
+ public User[] Assignees;
- [JsonPropertyName("assignees")]
- public User[] Assignees;
+ [JsonPropertyName("reviewers")]
+ public User[] Reviewers;
- [JsonPropertyName("reviewers")]
- public User[] Reviewers;
+ [JsonPropertyName("allow_collaboration")]
+ public bool? AllowCollaboration;
- [JsonPropertyName("allow_collaboration")]
- public bool? AllowCollaboration;
+ [JsonPropertyName("head_pipeline")]
+ public Pipeline HeadPipeline;
- [JsonPropertyName("head_pipeline")]
- public Pipeline HeadPipeline;
+ [JsonPropertyName("rebase_in_progress")]
+ public bool RebaseInProgress;
- [JsonPropertyName("rebase_in_progress")]
- public bool RebaseInProgress;
+ [JsonPropertyName("diverged_commits_count")]
+ public int? DivergedCommitsCount { get; set; }
- [JsonPropertyName("diverged_commits_count")]
- public int? DivergedCommitsCount { get; set; }
+ [JsonPropertyName("has_conflicts")]
+ public bool HasConflicts { get; set; }
- [JsonPropertyName("has_conflicts")]
- public bool HasConflicts { get; set; }
+ [JsonPropertyName("blocking_discussions_resolved")]
+ public bool BlockingDiscussionsResolved { get; set; }
- [JsonPropertyName("blocking_discussions_resolved")]
- public bool BlockingDiscussionsResolved { get; set; }
+ [JsonPropertyName("user")]
+ public MergeRequestUserInfo User { get; set; }
- [JsonPropertyName("user")]
- public MergeRequestUserInfo User { get; set; }
+ [JsonPropertyName("detailed_merge_status")]
+ public DynamicEnum DetailedMergeStatus { get; set; }
- public override string ToString()
- {
- return $"!{Id.ToStringInvariant()}: {Title}";
- }
- }
+ public override string ToString() => $"!{Id.ToStringInvariant()}: {Title}";
}
diff --git a/NGitLab/Models/MergeRequestUpdate.cs b/NGitLab/Models/MergeRequestUpdate.cs
index 7d048d7e..6a71ce36 100644
--- a/NGitLab/Models/MergeRequestUpdate.cs
+++ b/NGitLab/Models/MergeRequestUpdate.cs
@@ -31,6 +31,12 @@ public class MergeRequestUpdate
[JsonPropertyName("labels")]
public string Labels;
+ [JsonPropertyName("add_labels")]
+ public string AddLabels;
+
+ [JsonPropertyName("remove_labels")]
+ public string RemoveLabels;
+
[JsonPropertyName("milestone_id")]
public int? MilestoneId;
diff --git a/NGitLab/Models/Milestone.cs b/NGitLab/Models/Milestone.cs
index 6d9b8e87..2f7ed799 100644
--- a/NGitLab/Models/Milestone.cs
+++ b/NGitLab/Models/Milestone.cs
@@ -20,6 +20,12 @@ public class Milestone
[JsonPropertyName("due_date")]
public string DueDate;
+ [JsonPropertyName("group_id")]
+ public int? GroupId;
+
+ [JsonPropertyName("project_id")]
+ public int? ProjectId;
+
[JsonPropertyName("start_date")]
public string StartDate;
diff --git a/NGitLab/Models/Project.cs b/NGitLab/Models/Project.cs
index b4fc76d4..4a64c029 100644
--- a/NGitLab/Models/Project.cs
+++ b/NGitLab/Models/Project.cs
@@ -69,6 +69,9 @@ public class Project
[JsonPropertyName("issues_access_level")]
public string IssuesAccessLevel;
+ [JsonPropertyName("merge_pipelines_enabled")]
+ public bool MergePipelinesEnabled;
+
[JsonPropertyName("merge_requests_enabled")]
[Obsolete("Deprecated by GitLab. Use MergeRequestsAccessLevel instead")]
public bool MergeRequestsEnabled;
@@ -76,6 +79,9 @@ public class Project
[JsonPropertyName("merge_requests_access_level")]
public string MergeRequestsAccessLevel;
+ [JsonPropertyName("merge_trains_enabled")]
+ public bool MergeTrainsEnabled;
+
[JsonPropertyName("repository_access_level")]
public RepositoryAccessLevel RepositoryAccessLevel;
diff --git a/NGitLab/Models/ProjectCreate.cs b/NGitLab/Models/ProjectCreate.cs
index 71e9ed70..71dd1963 100644
--- a/NGitLab/Models/ProjectCreate.cs
+++ b/NGitLab/Models/ProjectCreate.cs
@@ -11,7 +11,6 @@ public class ProjectCreate
[JsonPropertyName("name")]
public string Name;
- [Required]
[JsonPropertyName("namespace_id")]
public string NamespaceId;
@@ -35,6 +34,9 @@ public class ProjectCreate
[JsonIgnore]
public bool WallEnabled;
+ [JsonPropertyName("merge_pipelines_enabled")]
+ public bool MergePipelinesEnabled;
+
[JsonPropertyName("merge_requests_enabled")]
[Obsolete("Deprecated by GitLab. Use MergeRequestsAccessLevel instead")]
public bool MergeRequestsEnabled;
@@ -42,6 +44,9 @@ public class ProjectCreate
[JsonPropertyName("merge_requests_access_level")]
public string MergeRequestsAccessLevel;
+ [JsonPropertyName("merge_trains_enabled")]
+ public bool MergeTrainsEnabled;
+
[JsonPropertyName("snippets_enabled")]
[Obsolete("Deprecated by GitLab. Use SnippetsAccessLevel instead")]
public bool SnippetsEnabled;
diff --git a/NGitLab/Models/ProjectUpdate.cs b/NGitLab/Models/ProjectUpdate.cs
index 0eda8afa..cd78f8b9 100644
--- a/NGitLab/Models/ProjectUpdate.cs
+++ b/NGitLab/Models/ProjectUpdate.cs
@@ -25,6 +25,9 @@ public sealed class ProjectUpdate
[JsonPropertyName("issues_access_level")]
public string IssuesAccessLeve { get; set; }
+ [JsonPropertyName("merge_pipelines_enabled")]
+ public bool MergePipelinesEnabled { get; set; }
+
[JsonPropertyName("merge_requests_enabled")]
[Obsolete("Deprecated by GitLab. Use MergeRequestsAccessLevel instead")]
public bool? MergeRequestsEnabled { get; set; }
@@ -32,6 +35,9 @@ public sealed class ProjectUpdate
[JsonPropertyName("merge_requests_access_level")]
public string MergeRequestsAccessLevel { get; set; }
+ [JsonPropertyName("merge_trains_enabled")]
+ public bool MergeTrainsEnabled { get; set; }
+
[JsonPropertyName("jobs_enabled")]
[Obsolete("Deprecated by GitLab. Use BuildsAccessLevel instead")]
public bool? JobsEnabled { get; set; }
diff --git a/NGitLab/NGitLab.csproj b/NGitLab/NGitLab.csproj
index 4fa8492c..00e52c6f 100644
--- a/NGitLab/NGitLab.csproj
+++ b/NGitLab/NGitLab.csproj
@@ -1,4 +1,4 @@
-
+
net461;netstandard2.0
@@ -22,6 +22,6 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
diff --git a/NGitLab/PublicAPI.Unshipped.txt b/NGitLab/PublicAPI.Unshipped.txt
index 28e05a85..4f9b3455 100644
--- a/NGitLab/PublicAPI.Unshipped.txt
+++ b/NGitLab/PublicAPI.Unshipped.txt
@@ -35,6 +35,10 @@ NGitLab.GetCommitsRequest.PerPage.get -> uint
NGitLab.GetCommitsRequest.PerPage.set -> void
NGitLab.GetCommitsRequest.RefName.get -> string
NGitLab.GetCommitsRequest.RefName.set -> void
+NGitLab.GetCommitsRequest.Since.get -> System.DateTime?
+NGitLab.GetCommitsRequest.Since.set -> void
+NGitLab.GetCommitsRequest.Until.get -> System.DateTime?
+NGitLab.GetCommitsRequest.Until.set -> void
NGitLab.GitLabClient
NGitLab.GitLabClient.AdvancedSearch.get -> NGitLab.ISearchClient
NGitLab.GitLabClient.Deployments.get -> NGitLab.IDeploymentClient
@@ -249,6 +253,7 @@ NGitLab.IIssueClient.ClosedBy(int projectId, int issueIid) -> System.Collections
NGitLab.IIssueClient.ClosedByAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse
NGitLab.IIssueClient.Create(NGitLab.Models.IssueCreate issueCreate) -> NGitLab.Models.Issue
NGitLab.IIssueClient.CreateAsync(NGitLab.Models.IssueCreate issueCreate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+NGitLab.IIssueClient.CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) -> bool
NGitLab.IIssueClient.Edit(NGitLab.Models.IssueEdit issueEdit) -> NGitLab.Models.Issue
NGitLab.IIssueClient.EditAsync(NGitLab.Models.IssueEdit issueEdit, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.IIssueClient.ForGroupsAsync(int groupId) -> NGitLab.GitLabCollectionResponse
@@ -263,6 +268,7 @@ NGitLab.IIssueClient.GetAsync(int projectId, NGitLab.Models.IssueQuery query) ->
NGitLab.IIssueClient.GetAsync(NGitLab.Models.IssueQuery query) -> NGitLab.GitLabCollectionResponse
NGitLab.IIssueClient.GetById(int issueId) -> NGitLab.Models.Issue
NGitLab.IIssueClient.GetByIdAsync(int issueId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+NGitLab.IIssueClient.LinkedToAsync(int projectId, int issueId) -> NGitLab.GitLabCollectionResponse
NGitLab.IIssueClient.Owned.get -> System.Collections.Generic.IEnumerable
NGitLab.IIssueClient.RelatedTo(int projectId, int issueIid) -> System.Collections.Generic.IEnumerable
NGitLab.IIssueClient.RelatedToAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse
@@ -362,6 +368,8 @@ NGitLab.IMergeRequestDiscussionClient
NGitLab.IMergeRequestDiscussionClient.Add(NGitLab.Models.MergeRequestDiscussionCreate discussion) -> NGitLab.Models.MergeRequestDiscussion
NGitLab.IMergeRequestDiscussionClient.All.get -> System.Collections.Generic.IEnumerable
NGitLab.IMergeRequestDiscussionClient.Delete(string discussionId, long commentId) -> void
+NGitLab.IMergeRequestDiscussionClient.Get(string id) -> NGitLab.Models.MergeRequestDiscussion
+NGitLab.IMergeRequestDiscussionClient.GetAsync(string id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.IMergeRequestDiscussionClient.Resolve(NGitLab.Models.MergeRequestDiscussionResolve resolve) -> NGitLab.Models.MergeRequestDiscussion
NGitLab.IMilestoneClient
NGitLab.IMilestoneClient.Activate(int milestoneId) -> NGitLab.Models.Milestone
@@ -371,6 +379,7 @@ NGitLab.IMilestoneClient.Close(int milestoneId) -> NGitLab.Models.Milestone
NGitLab.IMilestoneClient.Create(NGitLab.Models.MilestoneCreate milestone) -> NGitLab.Models.Milestone
NGitLab.IMilestoneClient.Delete(int milestoneId) -> void
NGitLab.IMilestoneClient.Get(NGitLab.Models.MilestoneQuery query) -> System.Collections.Generic.IEnumerable
+NGitLab.IMilestoneClient.GetMergeRequests(int milestoneId) -> System.Collections.Generic.IEnumerable
NGitLab.IMilestoneClient.Scope.get -> NGitLab.Impl.MilestoneScope
NGitLab.IMilestoneClient.this[int id].get -> NGitLab.Models.Milestone
NGitLab.IMilestoneClient.Update(int milestoneId, NGitLab.Models.MilestoneUpdate milestone) -> NGitLab.Models.Milestone
@@ -498,6 +507,7 @@ NGitLab.Impl.IssueClient.ClosedBy(int projectId, int issueIid) -> System.Collect
NGitLab.Impl.IssueClient.ClosedByAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse
NGitLab.Impl.IssueClient.Create(NGitLab.Models.IssueCreate issueCreate) -> NGitLab.Models.Issue
NGitLab.Impl.IssueClient.CreateAsync(NGitLab.Models.IssueCreate issueCreate, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
+NGitLab.Impl.IssueClient.CreateLinkBetweenIssues(int sourceProjectId, int sourceIssueId, int targetProjectId, int targetIssueId) -> bool
NGitLab.Impl.IssueClient.Edit(NGitLab.Models.IssueEdit issueEdit) -> NGitLab.Models.Issue
NGitLab.Impl.IssueClient.EditAsync(NGitLab.Models.IssueEdit issueEdit, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.Impl.IssueClient.ForGroupsAsync(int groupId) -> NGitLab.GitLabCollectionResponse
@@ -513,6 +523,7 @@ NGitLab.Impl.IssueClient.GetAsync(NGitLab.Models.IssueQuery query) -> NGitLab.Gi
NGitLab.Impl.IssueClient.GetById(int issueId) -> NGitLab.Models.Issue
NGitLab.Impl.IssueClient.GetByIdAsync(int issueId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.Impl.IssueClient.IssueClient(NGitLab.Impl.API api) -> void
+NGitLab.Impl.IssueClient.LinkedToAsync(int projectId, int issueId) -> NGitLab.GitLabCollectionResponse
NGitLab.Impl.IssueClient.Owned.get -> System.Collections.Generic.IEnumerable
NGitLab.Impl.IssueClient.RelatedTo(int projectId, int issueIid) -> System.Collections.Generic.IEnumerable
NGitLab.Impl.IssueClient.RelatedToAsync(int projectId, int issueIid) -> NGitLab.GitLabCollectionResponse
@@ -619,6 +630,8 @@ NGitLab.Impl.MergeRequestDiscussionClient
NGitLab.Impl.MergeRequestDiscussionClient.Add(NGitLab.Models.MergeRequestDiscussionCreate comment) -> NGitLab.Models.MergeRequestDiscussion
NGitLab.Impl.MergeRequestDiscussionClient.All.get -> System.Collections.Generic.IEnumerable
NGitLab.Impl.MergeRequestDiscussionClient.Delete(string discussionId, long commentId) -> void
+NGitLab.Impl.MergeRequestDiscussionClient.Get(string id) -> NGitLab.Models.MergeRequestDiscussion
+NGitLab.Impl.MergeRequestDiscussionClient.GetAsync(string id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.Impl.MergeRequestDiscussionClient.MergeRequestDiscussionClient(NGitLab.Impl.API api, string projectPath, int mergeRequestIid) -> void
NGitLab.Impl.MergeRequestDiscussionClient.Resolve(NGitLab.Models.MergeRequestDiscussionResolve resolve) -> NGitLab.Models.MergeRequestDiscussion
NGitLab.Impl.MethodType
@@ -638,6 +651,7 @@ NGitLab.Impl.MilestoneClient.Close(int milestoneId) -> NGitLab.Models.Milestone
NGitLab.Impl.MilestoneClient.Create(NGitLab.Models.MilestoneCreate milestone) -> NGitLab.Models.Milestone
NGitLab.Impl.MilestoneClient.Delete(int milestoneId) -> void
NGitLab.Impl.MilestoneClient.Get(NGitLab.Models.MilestoneQuery query) -> System.Collections.Generic.IEnumerable
+NGitLab.Impl.MilestoneClient.GetMergeRequests(int milestoneId) -> System.Collections.Generic.IEnumerable
NGitLab.Impl.MilestoneClient.MilestoneClient(NGitLab.Impl.API api, int projectId) -> void
NGitLab.Impl.MilestoneClient.Scope.get -> NGitLab.Impl.MilestoneScope
NGitLab.Impl.MilestoneClient.this[int id].get -> NGitLab.Models.Milestone
@@ -671,6 +685,7 @@ NGitLab.Impl.PipelineClient.GetTestReportsSummary(int pipelineId) -> NGitLab.Mod
NGitLab.Impl.PipelineClient.GetVariables(int pipelineId) -> System.Collections.Generic.IEnumerable
NGitLab.Impl.PipelineClient.GetVariablesAsync(int pipelineId) -> NGitLab.GitLabCollectionResponse
NGitLab.Impl.PipelineClient.PipelineClient(NGitLab.Impl.API api, int projectId) -> void
+NGitLab.Impl.PipelineClient.RetryAsync(int pipelineId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.Impl.PipelineClient.Search(NGitLab.Models.PipelineQuery query) -> System.Collections.Generic.IEnumerable
NGitLab.Impl.PipelineClient.SearchAsync(NGitLab.Models.PipelineQuery query) -> NGitLab.GitLabCollectionResponse
NGitLab.Impl.PipelineClient.this[int id].get -> NGitLab.Models.Pipeline
@@ -843,6 +858,7 @@ NGitLab.IPipelineClient.GetTestReports(int pipelineId) -> NGitLab.Models.TestRep
NGitLab.IPipelineClient.GetTestReportsSummary(int pipelineId) -> NGitLab.Models.TestReportSummary
NGitLab.IPipelineClient.GetVariables(int pipelineId) -> System.Collections.Generic.IEnumerable
NGitLab.IPipelineClient.GetVariablesAsync(int pipelineId) -> NGitLab.GitLabCollectionResponse
+NGitLab.IPipelineClient.RetryAsync(int pipelineId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
NGitLab.IPipelineClient.Search(NGitLab.Models.PipelineQuery query) -> System.Collections.Generic.IEnumerable
NGitLab.IPipelineClient.SearchAsync(NGitLab.Models.PipelineQuery query) -> NGitLab.GitLabCollectionResponse
NGitLab.IPipelineClient.this[int id].get -> NGitLab.Models.Pipeline
@@ -1365,6 +1381,20 @@ NGitLab.Models.DeploymentStatus.created = 0 -> NGitLab.Models.DeploymentStatus
NGitLab.Models.DeploymentStatus.failed = 3 -> NGitLab.Models.DeploymentStatus
NGitLab.Models.DeploymentStatus.running = 1 -> NGitLab.Models.DeploymentStatus
NGitLab.Models.DeploymentStatus.success = 2 -> NGitLab.Models.DeploymentStatus
+NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.BlockedStatus = 0 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.BrokenStatus = 1 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.Checking = 2 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.CiMustPass = 4 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.CiStillRunning = 5 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.DiscussionsNotResolved = 6 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.DraftStatus = 7 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.ExternalStatusChecks = 8 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.Mergeable = 9 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.NotApproved = 10 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.NotOpen = 11 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.PoliciesDenied = 12 -> NGitLab.Models.DetailedMergeStatus
+NGitLab.Models.DetailedMergeStatus.Unchecked = 3 -> NGitLab.Models.DetailedMergeStatus
NGitLab.Models.Diff
NGitLab.Models.Diff.AMode -> string
NGitLab.Models.Diff.BMode -> string
@@ -2049,6 +2079,8 @@ NGitLab.Models.MergeRequest.ClosedAt -> System.DateTime?
NGitLab.Models.MergeRequest.ClosedBy -> NGitLab.Models.User
NGitLab.Models.MergeRequest.CreatedAt -> System.DateTime
NGitLab.Models.MergeRequest.Description -> string
+NGitLab.Models.MergeRequest.DetailedMergeStatus.get -> NGitLab.DynamicEnum
+NGitLab.Models.MergeRequest.DetailedMergeStatus.set -> void
NGitLab.Models.MergeRequest.DiffRefs -> NGitLab.Models.DiffRefs
NGitLab.Models.MergeRequest.DivergedCommitsCount.get -> int?
NGitLab.Models.MergeRequest.DivergedCommitsCount.set -> void
@@ -2261,6 +2293,7 @@ NGitLab.Models.MergeRequestStateEvent.close = 0 -> NGitLab.Models.MergeRequestSt
NGitLab.Models.MergeRequestStateEvent.merge = 2 -> NGitLab.Models.MergeRequestStateEvent
NGitLab.Models.MergeRequestStateEvent.reopen = 1 -> NGitLab.Models.MergeRequestStateEvent
NGitLab.Models.MergeRequestUpdate
+NGitLab.Models.MergeRequestUpdate.AddLabels -> string
NGitLab.Models.MergeRequestUpdate.AllowCollaboration -> bool?
NGitLab.Models.MergeRequestUpdate.AssigneeId -> int?
NGitLab.Models.MergeRequestUpdate.AssigneeIds -> int[]
@@ -2269,6 +2302,7 @@ NGitLab.Models.MergeRequestUpdate.Labels -> string
NGitLab.Models.MergeRequestUpdate.MergeRequestUpdate() -> void
NGitLab.Models.MergeRequestUpdate.MilestoneId -> int?
NGitLab.Models.MergeRequestUpdate.NewState -> string
+NGitLab.Models.MergeRequestUpdate.RemoveLabels -> string
NGitLab.Models.MergeRequestUpdate.RemoveSourceBranch -> bool?
NGitLab.Models.MergeRequestUpdate.ReviewerIds -> int[]
NGitLab.Models.MergeRequestUpdate.SourceBranch -> string
@@ -2305,9 +2339,11 @@ NGitLab.Models.Milestone
NGitLab.Models.Milestone.CreatedAt -> System.DateTime
NGitLab.Models.Milestone.Description -> string
NGitLab.Models.Milestone.DueDate -> string
+NGitLab.Models.Milestone.GroupId -> int?
NGitLab.Models.Milestone.Id -> int
NGitLab.Models.Milestone.Iid -> int
NGitLab.Models.Milestone.Milestone() -> void
+NGitLab.Models.Milestone.ProjectId -> int?
NGitLab.Models.Milestone.StartDate -> string
NGitLab.Models.Milestone.State -> string
NGitLab.Models.Milestone.Title -> string
@@ -2532,8 +2568,10 @@ NGitLab.Models.Project.LastActivityAt -> System.DateTime
NGitLab.Models.Project.LfsEnabled -> bool
NGitLab.Models.Project.Links -> NGitLab.Models.ProjectLinks
NGitLab.Models.Project.MergeMethod -> string
+NGitLab.Models.Project.MergePipelinesEnabled -> bool
NGitLab.Models.Project.MergeRequestsAccessLevel -> string
NGitLab.Models.Project.MergeRequestsEnabled -> bool
+NGitLab.Models.Project.MergeTrainsEnabled -> bool
NGitLab.Models.Project.Mirror -> bool
NGitLab.Models.Project.MirrorOverwritesDivergedBranches -> bool
NGitLab.Models.Project.MirrorTriggerBuilds -> bool
@@ -2580,8 +2618,10 @@ NGitLab.Models.ProjectCreate.Description -> string
NGitLab.Models.ProjectCreate.ImportUrl -> string
NGitLab.Models.ProjectCreate.IssuesAccessLevel -> string
NGitLab.Models.ProjectCreate.IssuesEnabled -> bool
+NGitLab.Models.ProjectCreate.MergePipelinesEnabled -> bool
NGitLab.Models.ProjectCreate.MergeRequestsAccessLevel -> string
NGitLab.Models.ProjectCreate.MergeRequestsEnabled -> bool
+NGitLab.Models.ProjectCreate.MergeTrainsEnabled -> bool
NGitLab.Models.ProjectCreate.Name -> string
NGitLab.Models.ProjectCreate.NamespaceId -> string
NGitLab.Models.ProjectCreate.Path -> string
@@ -2748,10 +2788,14 @@ NGitLab.Models.ProjectUpdate.JobsEnabled.get -> bool?
NGitLab.Models.ProjectUpdate.JobsEnabled.set -> void
NGitLab.Models.ProjectUpdate.LfsEnabled.get -> bool?
NGitLab.Models.ProjectUpdate.LfsEnabled.set -> void
+NGitLab.Models.ProjectUpdate.MergePipelinesEnabled.get -> bool
+NGitLab.Models.ProjectUpdate.MergePipelinesEnabled.set -> void
NGitLab.Models.ProjectUpdate.MergeRequestsAccessLevel.get -> string
NGitLab.Models.ProjectUpdate.MergeRequestsAccessLevel.set -> void
NGitLab.Models.ProjectUpdate.MergeRequestsEnabled.get -> bool?
NGitLab.Models.ProjectUpdate.MergeRequestsEnabled.set -> void
+NGitLab.Models.ProjectUpdate.MergeTrainsEnabled.get -> bool
+NGitLab.Models.ProjectUpdate.MergeTrainsEnabled.set -> void
NGitLab.Models.ProjectUpdate.Name.get -> string
NGitLab.Models.ProjectUpdate.Name.set -> void
NGitLab.Models.ProjectUpdate.OnlyAllowMergeIfAllDiscussionsAreResolved.get -> bool?
diff --git a/README.md b/README.md
index 561643b1..608a6ea3 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,11 @@
## What is NGitLab?
-*NGitLab* is a .NET REST client implementation of GitLab API with no external dependencies.
+`NGitLab` is a .NET REST client implementation for the GitLab API.
## Usage
-It's a wrapper of REST api. Read the [GitLab docs](https://github.com/gitlabhq/gitlabhq/tree/master/doc/api) and start using by creating a GitLabClient instance:
+Start by creating a `GitLabClient` instance:
```csharp
var client = new GitLabClient("https://mygitlab.example.com", "your_private_token");
@@ -14,13 +14,17 @@ var client = new GitLabClient("https://mygitlab.example.com", "your_private_toke
Then use its properties. You can obtain the private token in your account page. You may want to create a custom user for the API usage.
+For further info about the GitLab API, refer to [the official documentation](https://docs.gitlab.com/ee/api/rest/)
+
## Where can I get it?
-Get it from [NuGet](https://www.nuget.org/packages/NGitLab). You can simply install it with the Package Manager console:
+Get it from [nuget.org](https://www.nuget.org/packages/NGitLab). You can simply install it using the `dotnet` CLI:
- PM> Install-Package NGitLab
+```PowerShell
+dotnet add package NGitLab
+```
-## Unit-Test
+## Running Unit Tests locally
- Install Docker on your machine
- It's recommended to use WSL version 2: https://docs.microsoft.com/en-us/windows/wsl/install-win10