Garrard.GitLab 1.0.9

dotnet add package Garrard.GitLab --version 1.0.9
                    
NuGet\Install-Package Garrard.GitLab -Version 1.0.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Garrard.GitLab" Version="1.0.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Garrard.GitLab" Version="1.0.9" />
                    
Directory.Packages.props
<PackageReference Include="Garrard.GitLab" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Garrard.GitLab --version 1.0.9
                    
#r "nuget: Garrard.GitLab, 1.0.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Garrard.GitLab@1.0.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Garrard.GitLab&version=1.0.9
                    
Install as a Cake Addin
#tool nuget:?package=Garrard.GitLab&version=1.0.9
                    
Install as a Cake Tool

Garrard.GitLab

A .NET library for working with GitLab groups, projects, variables, and repositories via the GitLab REST API.

Installation

dotnet add package Garrard.GitLab

Install a specific version:

dotnet add package Garrard.GitLab --version 1.0.9

File-based Apps:

#:package Garrard.GitLab@1.0.3

Or add directly to your project file:

<PackageReference Include="Garrard.GitLab" Version="1.0.9" />

Quick Start

Register the library with your DI container once at startup:

using Garrard.GitLab.Library;
using Garrard.GitLab.Library.Enums;  // AccessLevel, ProjectAccessTokenScope

services.AddGarrardGitLab(opts =>
{
    opts.Pat    = "your-gitlab-personal-access-token";  // required
    opts.Domain = "gitlab.com";                         // optional, defaults to gitlab.com
});

Then inject the client(s) you need:

public class MyService(GroupClient groupClient, ProjectClient projectClient)
{
    public async Task Run()
    {
        var result = await groupClient.FindGroups("my-team");

        if (result.IsSuccess)
        {
            foreach (var group in result.Value)
                Console.WriteLine($"{group.Name} ({group.Id})");
        }
        else
        {
            Console.WriteLine($"Error: {result.Error}");
        }
    }
}

Configuration

Options are bound from the GitLab configuration section. Using appsettings.json:

{
  "GitLab": {
    "Pat": "glpat-xxxxxxxxxxxxxxxxxxxx",
    "Domain": "gitlab.mycompany.com"
  }
}

Or environment variables:

GitLab__Pat=glpat-xxxxxxxxxxxxxxxxxxxx
GitLab__Domain=gitlab.mycompany.com

Clients

All methods return Task<Result<T>> from CSharpFunctionalExtensions — they never throw. Check result.IsSuccess, result.Value, and result.Error.

GroupClient

// Find groups by exact name or ID
var groups = await groupClient.FindGroups("my-team");

// Search groups using a partial/wildcard pattern
var matches = await groupClient.SearchGroups("team-");

// Get all subgroups beneath a group
var subgroups = await groupClient.GetSubgroups("parent-group");

// Create a top-level group
var newGroup = await groupClient.CreateGitLabGroup("My New Team");

// Create a subgroup under an existing group
var subgroup = await groupClient.CreateGitLabGroup("Backend", parentId: 42);

ProjectClient

// Get all projects in a group (includes subgroups by default)
var projects = await projectClient.GetProjectsInGroup("my-team");

// Create a new project (optionally in a group)
var project = await projectClient.CreateGitLabProject("my-app", groupId: 42);

// Transfer a project to a different group
var transfer = await projectClient.TransferProjectToGroupOrNamespace(projectId: 99, "target-group");

// Project variables (isHidden: true by default — value hidden after creation)
var vars      = await projectClient.GetProjectVariables(projectId: 99);
var variable  = await projectClient.GetProjectVariable(99, "API_KEY");
var upserted  = await projectClient.CreateOrUpdateProjectVariable(99, "API_KEY", "secret");
var deleted   = await projectClient.DeleteProjectVariable(99, "API_KEY");

// Search projects by partial name or namespace (paginated)
var page1 = await projectClient.SearchProjects(search: "my-app", page: 1, perPage: 20);
if (page1.IsSuccess)
{
    Console.WriteLine($"Page {page1.Value.Page} of {page1.Value.TotalPages} ({page1.Value.TotalItems} total)");
    foreach (var p in page1.Value.Items)
        Console.WriteLine($"  {p.PathWithNamespace} (ID: {p.Id})");
}

// Look up a project by exact ID
var byId = await projectClient.SearchProjects(id: 99);

// Create a project access token
var tokenResult = await projectClient.CreateProjectAccessToken(
    projectId:   "99",
    name:        "GL_TOKEN",                                 // default
    scopes:      ProjectAccessTokenScope.WriteRepository,    // default
    accessLevel: AccessLevel.Maintainer,                     // default (40)
    expiresAt:   new DateOnly(2027, 1, 1));                  // default: +1 year

if (tokenResult.IsSuccess)
{
    Console.WriteLine($"Token: {tokenResult.Value.Token}");
    Console.WriteLine($"Expires: {tokenResult.Value.ExpiresAt}");
    Console.WriteLine($"Access level: {tokenResult.Value.AccessLevel}");
    Console.WriteLine($"Scopes: {string.Join(", ", tokenResult.Value.Scopes)}");
}
ProjectAccessTokenScope (flags enum)
Value API scope string
Api api
ReadApi read_api
ReadRepository read_repository
WriteRepository write_repository
ReadRegistry read_registry
WriteRegistry write_registry
ReadPackageRegistry read_package_registry
WritePackageRegistry write_package_registry
CreateRunner create_runner
ManageRunner manage_runner
AiFeatures ai_features
K8sProxy k8s_proxy
ReadObservability read_observability
WriteObservability write_observability

Combine scopes with |:

ProjectAccessTokenScope.WriteRepository | ProjectAccessTokenScope.ReadApi
AccessLevel enum
Value Level
Guest 10
Reporter 20
Developer 30
Maintainer 40 (default)
Owner 50

GroupVariableClient

// Get a group-level CI/CD variable
var variable = await groupVariableClient.GetGroupVariable("1607", "DEPLOY_TOKEN");

// Create or update a group variable
var result = await groupVariableClient.CreateOrUpdateGroupVariable(
    groupId:          "1607",
    key:              "DEPLOY_TOKEN",
    value:            "my-secret",
    variableType:     "env_var",   // optional
    isProtected:      false,       // optional
    isMasked:         true,        // optional
    environmentScope: "*"          // optional
);

SummaryClient

// Overview of a group (subgroup count, project count)
var groupSummary = await summaryClient.GetGroupSummary("my-team");

// Summaries of all projects within a group
var projectSummaries = await summaryClient.GetGroupProjectsSummary("my-team", includeSubgroups: true);

// Summary of a single project (variable count etc.)
var projectSummary = await summaryClient.GetProjectSummary(projectId: 99);

GitClient

// Create and clone a new GitLab project, then push an initial commit
var project = await gitClient.CreateGitLabProject("my-app", groupId: 42);
gitClient.DownloadGitRepository("https://github.com/org/template.git", "/tmp/template", "main");
gitClient.CloneGitLabProject(project.Value.HttpUrlToRepo, "/tmp/new-project");
gitClient.BranchCommitPushChanges("/tmp/new-project", "initial commit", "main");

// Transfer a project
await gitClient.TransferProjectToGroupOrNamespace(project.Value.Id, "target-group");

FileClient

FileClient has no dependencies and can be instantiated directly (new FileClient()) or injected.

// Create a file
fileClient.CreateFileWithContent("/tmp/new-project", "README.md", "# My App");

// Copy files between directories (excludes .git/)
fileClient.CopyFiles("/tmp/template", "/tmp/new-project");

// Replace a placeholder value in a file
var result = await fileClient.ReplacePlaceholderInFile(
    "./.gitlab-ci.yml",
    "WORKSPACE_NAME",
    "\"<enter-workload-name>\"",
    "\"my-workspace\"",
    ":"
);

// Clean up
fileClient.RemoveTempFolder("/tmp/template");

Progress callbacks

All async methods accept an optional Action<string>? onMessage parameter for progress output:

var result = await groupClient.GetSubgroups("my-team", onMessage: Console.WriteLine);

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.9 101 3/3/2026
1.0.8 87 3/2/2026
1.0.7 97 3/2/2026
1.0.6 99 3/2/2026
1.0.5 90 3/2/2026
1.0.4 93 3/2/2026
1.0.3 95 3/2/2026
1.0.2 89 3/1/2026
1.0.1 88 3/1/2026
1.0.0 89 3/1/2026
0.0.21 624 10/25/2025
0.0.20 363 3/29/2025
0.0.19 237 3/27/2025
0.0.18 548 3/26/2025
0.0.17 236 3/19/2025
0.0.16 228 3/18/2025
0.0.15 274 3/7/2025
0.0.14 298 3/5/2025
0.0.13 271 3/5/2025
0.0.1 199 3/8/2025
Loading failed

Please refer to the README