Git: From Versioned Inputs to Typed Observations
The Git integration exposes source revisions, working-tree information and repository observations. Its CLI exposes several machine protocols, not one generic “Git output” schema.
The integration demonstrates why a parser factory must consider both version and command format, and why stderr can carry structured data useful to the application.
Acquisition handles Git's help behavior
The Design executable collects git/git tags, resolves version-specific build recipes and uses GitHelpParser. Its default collection minimum is 2.30.0.
A custom middleware overrides root git -h with git help -a to discover commands. It captures both streams and tolerates the nonzero status used by Git help, rather than treating every help query exit as an acquisition failure.
The custom callback uses DesignEnvironment.HelpArguments so catalogue variables reach the queried process. Replay uses cached text and the same parser without rebuilding Git.
Generated syntax and contracts
GitDescriptor uses FlagValueSeparator = "=". Optional-valued flags such as porcelain must be attached: Porcelain = "2" produces --porcelain=2.
Commands and GitEnvironment belong to Git.Contracts. Builders/client, parsers, strategies and the concrete runner remain implementation types. Existing namespaces stay unchanged.
Do not use a hand-quoted shell string as the expected output of the generated command. Validate argument elements and final process path segments separately.
A complete porcelain example
The following offline example requests porcelain v2 and feeds an untracked-file observation plus a reviewed diagnostic shape. It opens no repository.
using FrenchExDev.Net.BinaryWrapper;
using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.BinaryWrapper.Testing;
using FrenchExDev.Net.Git;
var version = SemanticVersion.Parse("2.53.0");
var binding = new BinaryBinding
{
Identifier = new BinaryIdentifier("git"),
ExecutablePath = "unused",
DetectedVersion = version,
};
var command = new GitStatusCommand(version)
{
Porcelain = "2",
Branch = true,
Environment = new GitEnvironment { Trace2Event = "1" },
};
var executor = new CommandExecutor(new DictionaryBinaryResolver(binding),
new FakeProcessRunner([
new OutputLine("? new-file.txt", OutputSource.StdOut),
new OutputLine("warning: example diagnostic", OutputSource.StdErr),
]));
await foreach (var evt in executor.StreamAsync(
binding.Identifier, command, GitOutputParsers.Default))
{
if (evt is GitUntrackedPathObserved path)
Console.WriteLine($"Untracked: {path.Path}");
if (evt is GitDiagnosticObserved diagnostic)
Console.WriteLine(diagnostic.Message);
if (evt.OutputLine is { } line)
Console.WriteLine($"{line.Source}: {line.Text}");
if (evt is GitCommandExited exited)
Console.WriteLine($"Git exit: {exited.ExitCode}");
}using FrenchExDev.Net.BinaryWrapper;
using FrenchExDev.Net.BinaryWrapper.Lib;
using FrenchExDev.Net.BinaryWrapper.Testing;
using FrenchExDev.Net.Git;
var version = SemanticVersion.Parse("2.53.0");
var binding = new BinaryBinding
{
Identifier = new BinaryIdentifier("git"),
ExecutablePath = "unused",
DetectedVersion = version,
};
var command = new GitStatusCommand(version)
{
Porcelain = "2",
Branch = true,
Environment = new GitEnvironment { Trace2Event = "1" },
};
var executor = new CommandExecutor(new DictionaryBinaryResolver(binding),
new FakeProcessRunner([
new OutputLine("? new-file.txt", OutputSource.StdOut),
new OutputLine("warning: example diagnostic", OutputSource.StdErr),
]));
await foreach (var evt in executor.StreamAsync(
binding.Identifier, command, GitOutputParsers.Default))
{
if (evt is GitUntrackedPathObserved path)
Console.WriteLine($"Untracked: {path.Path}");
if (evt is GitDiagnosticObserved diagnostic)
Console.WriteLine(diagnostic.Message);
if (evt.OutputLine is { } line)
Console.WriteLine($"{line.Source}: {line.Text}");
if (evt is GitCommandExited exited)
Console.WriteLine($"Git exit: {exited.ExitCode}");
}Setting Trace2Event configures the invocation; this fixture deliberately supplies no trace rows. A live invocation's traces arrive according to Git's configured destination.
Supported protocol families
| Format | Representative observations |
|---|---|
| Status porcelain v1/v2 | Path status, untracked/ignored paths, branch identity/divergence, renames, conflicts |
| Push porcelain | Destination, reference update/rejection, report completion |
| Fetch porcelain | Old/new object IDs, status and local reference |
| Show-ref and ls-remote | Object IDs, references and symbolic relationships |
| Diff name-status/numstat | Changed paths, rename relationships, line counts and binary markers |
| Trace2 JSON | Sessions, regions, child/thread lifecycle, parameters and data |
| Reviewed stderr text | Diagnostics and complete progress rows |
The parser validates structured fields such as octal modes and preserves quoted wire spelling through the original line. Unknown status headers or JSON fields can remain in generic evidence.
NUL-delimited -z formats need framing beyond this logical-line transport and remain raw. Arbitrary blobs, hook output, external diff drivers and localized prose are not universal Git events.
Trace2 has its own protocol version
The source catalogue inspects Git 2.30.0 and 2.53.0 emitter revisions. Their Trace2 format versions differ. GitTrace2Version.FormatVersion reports that wire version separately from an event's semantic Git OutputVersion.
Trace2 JSON can be directed to stderr through GitEnvironment.Trace2Event = "1". Records retain session/thread identity, optional source fields and complete cloned JSON. Missing optional values are not fabricated.
Trace2 exit, atexit or child-exit messages describe their own reporting contexts. GitCommandExited is the runner's actual terminal status.
Version profiles
The implemented git-2.30-2.40 interval starts at 2.30.0 and ends before 2.41.0. git-2.41+ starts at 2.41.0 and ends before 3.0.0-0. Fetch porcelain is enabled only in the latter.
These are the strategy's compatibility intervals, backed by the reviewed protocols. They should not be described as a live test of every release in the range. Unknown or unmatched versions retain GitOutputLine and terminal status.
Typed Environment
The catalogue covers repository/worktree paths, index/object settings, identity/configuration inputs, tracing and related variables. Numeric and path-list converters preserve their specific wire syntax.
Directory maps to GIT_DIR; WorkTree maps to GIT_WORK_TREE. These configure Git's repository selection and are different from changing a process working directory.
The generated serializer uses the command target for compatibility. Binding defaults and command overrides then follow the shared environment merge rules.
Validation and consumption
Tests verify porcelain/reference/diff rows, quoting, Trace2 fields, stderr progress, profile boundaries, fresh state, raw fallback and generated-command integration. Local-repository checks are a separate live layer.
Source revision, working-tree state and command success are separate facts. A push report milestone or progress “done” does not replace the actual process exit.
Source snapshot
Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.
Git/src/FrenchExDev.Net.Git.Design/Program.csGit/src/FrenchExDev.Net.Git.Contracts/FrenchExDev.Net.Git.Contracts.csprojGit/src/FrenchExDev.Net.Git/FrenchExDev.Net.Git.csprojGit/doc/TYPED-OUTPUT.mdGit/test/FrenchExDev.Net.Git.Tests
Previous: DockerCompose · Implementation index · Series index · Next: Packer