Skip to main content

Git Package

Git repository information, versioning, and strongly typed Git commands.

Installation​

dotnet add package ModularPipelines.Git

Required command-line tool: git. It must be installed and available on PATH when the pipeline runs.

Context entry points​

Use the discoverable context.Tools surface from a module:

  • context.Tools.Git

Module example​

using ModularPipelines;
using ModularPipelines.Git.Options;

public class UseGitModule : Module<CommandResult>
{
protected override async Task<CommandResult> ExecuteAsync(
IModuleContext context,
CancellationToken cancellationToken)
{
return await context.Tools.Git.Commands.WorkingTree.StatusAsync(
new GitStatusOptions
{
Short = true,
},
cancellationToken: cancellationToken);
}
}

The package exposes strongly typed options records for its supported CLI commands.

Shared execution options​

All command options inherit root execution settings from GitOptions. These settings appear before the command name. For example:

await context.Tools.Git.Commands.WorkingTree.StatusAsync(new GitStatusOptions
{
ChangeDirectories = ["checkout", "application"],
Configuration = [("core.quotepath", "false")],
NoOptionalLocks = true,
Short = true,
}, cancellationToken: cancellationToken);

ChangeDirectories repeats -C in collection order. Each relative directory resolves against the previous directory; directory changes precede other root settings. Configuration repeats -c key=value without collapsing duplicate keys. Use an empty value to clear a setting or "true" for Git's implicit boolean form. Values use KeyValue and are registered for secret masking, including HTTP authorization headers. Secret masking does not remove values from Git's process arguments. Users able to inspect those arguments may read credentials. Use ConfigEnv for credential-bearing values that must not appear in Git's arguments. ConfigEnv repeats --config-env=name=ENVIRONMENT_VARIABLE; it contains variable names, not their secret contents.

GitDirectory, WorkTree, Namespace, and ExecPath use equals-separated values. ExecPath requires a value; its bare path-reporting form is not an inherited setting. Pager controls, replacement-object controls, optional-lock controls, pathspec settings, AttrSource, NoLazyFetch, and NoAdvice are also shared. The installed Git version must support each selected option. Command-local switches retain their separate scope: GitRevParseOptions.GitDir reports a path, while GitDirectory selects the repository before rev-parse executes. BareRepository sets root --bare; command-local Bare properties such as GitCloneOptions.Bare still follow their command.

GitBaseOptions retains version and path-reporting actions for Repository.GitAsync. These actions are not inherited by ordinary commands. Existing GitBaseOptions.GitDir and .Bare initializers migrate to .GitDirectory and .BareRepository respectively; other shared properties retain their names through inheritance.

The Git integration deliberately retains its handwritten grouped facade and option ownership. Its scraper captures root usage separately from git help -a, validates the complete command tree, and refreshes coverage metadata without replacing these records. See the Git command reference for root-option semantics.

Repository information​

When the pipeline always runs inside a repository, use GetRequiredInfoAsync. It throws an InvalidOperationException when Git information is unavailable, for example outside a repository or when git is not installed:

var repository = await context.Tools.Git.Information.GetRequiredInfoAsync(cancellationToken);
var branch = repository.BranchName;
var commit = repository.LastCommitSha;

Properties such as BranchName and LastCommitSha remain nullable, for example on a detached HEAD or in a repository without commits. Use GetInfoAsync when the pipeline must handle running outside a repository; it returns null instead of throwing.

Run only when paths change​

Use RunIfChangedAttribute to run a module when at least one repository-relative glob matches a path changed since the merge base with origin/main:

using ModularPipelines.Git.Attributes;

[RunIfChanged("src/MyService/**", "test/MyService.Tests/**")]
public class TestMyServiceModule : Module<CommandResult>
{
// ...
}

Set another base revision with the named Base property:

[RunIfChanged("src/**", Base = "origin/release")]

For imperative checks, use the same cached changed-path set through the Git context:

var shouldBuild = await context.Tools.Git.Changes.HasChangesAsync(
["src/MyService/**", "Directory.Packages.props"],
cancellationToken: cancellationToken);

Each base revision is resolved with git merge-base, and its git diff --name-only result is combined with untracked, non-ignored files once per pipeline run. The comparison therefore includes committed, staged, unstaged, and new files. A pattern without * or ? matches both that exact path and paths beneath it, so src/MyService can be used instead of src/MyService/**. If the base revision is unavailable (for example, in a shallow checkout without origin/main), the condition logs a warning and conservatively runs the module.

Custom command runners​

If you replace IGitCommandRunner and use changed-path checks, implement IRawGitCommandRunner on the same class. GitChanges uses its untrimmed output to preserve NUL-delimited Git paths exactly, including leading or trailing whitespace.

builder.Services.AddSingleton<IGitCommandRunner, CustomGitCommandRunner>();

CustomGitCommandRunner must implement both interfaces on the same class.