Skip to content

Feature Request: Strongly Typed Git Command API #3

@StevenTCramer

Description

@StevenTCramer

Feature Request: Strongly Typed Git Command API

Summary

Add a strongly typed, fluent API for Git commands to TimeWarp.Cli, following the same architectural patterns established for DotNet commands. This would provide an elegant, IntelliSense-friendly interface for common Git operations while maintaining TimeWarp.Cli's philosophy of graceful error handling and async-first design.

Motivation

Currently, TimeWarp.Cli provides excellent fluent APIs for DotNet commands (e.g., DotNet.Build().WithConfiguration("Release").ExecuteAsync()), but Git operations require manual string construction:

// Current approach - verbose and error-prone
await Run("git", "commit", "-m", "My commit message", "--author", "John Doe <john@example.com>").ExecuteAsync();
await Run("git", "push", "origin", "main").ExecuteAsync();
await Run("git", "checkout", "-b", "feature/new-branch").ExecuteAsync();

A strongly typed Git API would provide:

  • Type Safety: Catch errors at compile-time instead of runtime
  • IntelliSense Support: Discoverability of options and parameters
  • Consistency: Unified API style with existing DotNet commands
  • Maintainability: Easier to refactor and maintain Git operations

Proposed API Design

Following TimeWarp.Cli's established patterns, the Git API should provide:

Core Git Operations

// Status
var status = await Git.Status().GetStringAsync();
var statusLines = await Git.Status().WithPorcelain().GetLinesAsync();

// Commit
await Git.Commit()
    .WithMessage("feat: add new feature")
    .WithAuthor("John Doe", "john@example.com")
    .WithAllowEmpty()
    .ExecuteAsync();

// Push/Pull
await Git.Push()
    .ToRemote("origin")
    .WithBranch("main")
    .WithForce()
    .ExecuteAsync();

await Git.Pull()
    .FromRemote("origin")
    .WithBranch("main")
    .WithRebase()
    .ExecuteAsync();

// Branch Operations
await Git.Checkout()
    .ToBranch("main")
    .ExecuteAsync();

await Git.Checkout()
    .CreateBranch("feature/new-feature")
    .ExecuteAsync();

var branches = await Git.Branch()
    .WithRemotes()
    .GetLinesAsync();

// Add/Reset
await Git.Add()
    .WithFiles("*.cs", "README.md")
    .ExecuteAsync();

await Git.Add()
    .WithAllFiles()
    .ExecuteAsync();

await Git.Reset()
    .WithHard()
    .ToCommit("HEAD~1")
    .ExecuteAsync();

Advanced Operations

// Log with filtering
var commits = await Git.Log()
    .WithMaxCount(10)
    .WithOneline()
    .WithAuthor("john@example.com")
    .WithSince("2024-01-01")
    .GetLinesAsync();

// Merge
await Git.Merge()
    .WithBranch("feature/branch")
    .WithMessage("Merge feature branch")
    .WithNoFastForward()
    .ExecuteAsync();

// Rebase
await Git.Rebase()
    .OntoCommit("main")
    .WithInteractive()
    .ExecuteAsync();

// Remote operations
await Git.Remote()
    .Add("upstream", "https://github.com/original/repo.git")
    .ExecuteAsync();

var remotes = await Git.Remote()
    .WithVerbose()
    .GetLinesAsync();

// Clone
await Git.Clone()
    .FromUrl("https://github.com/user/repo.git")
    .ToDirectory("./local-repo")
    .WithDepth(1)
    .WithSingleBranch()
    .ExecuteAsync();

Configuration and Submodules

// Config
await Git.Config()
    .SetGlobal("user.name", "John Doe")
    .ExecuteAsync();

var userName = await Git.Config()
    .Get("user.name")
    .GetStringAsync();

// Submodules
await Git.Submodule()
    .Add("https://github.com/user/library.git", "lib/library")
    .ExecuteAsync();

await Git.Submodule()
    .Update()
    .WithInit()
    .WithRecursive()
    .ExecuteAsync();

Implementation Architecture

Partial Class Structure

Following the DotNet command pattern:

Source/TimeWarp.Cli/GitCommands/
├── Git.cs                    // Main partial class
├── Git.Status.cs            // Status command builder
├── Git.Commit.cs            // Commit command builder  
├── Git.Push.cs              // Push command builder
├── Git.Pull.cs              // Pull command builder
├── Git.Checkout.cs          // Checkout command builder
├── Git.Branch.cs            // Branch command builder
├── Git.Add.cs               // Add command builder
├── Git.Reset.cs             // Reset command builder
├── Git.Log.cs               // Log command builder
├── Git.Merge.cs             // Merge command builder
├── Git.Rebase.cs            // Rebase command builder
├── Git.Remote.cs            // Remote command builder
├── Git.Clone.cs             // Clone command builder
├── Git.Config.cs            // Config command builder
├── Git.Submodule.cs         // Submodule command builder
└── Git.Extensions.cs        // Common extensions and utilities

Builder Pattern Implementation

Each command would follow the established pattern:

public static partial class Git
{
    public static GitCommitBuilder Commit() => new GitCommitBuilder();
    public static GitCommitBuilder Commit(string message) => new GitCommitBuilder().WithMessage(message);
}

public class GitCommitBuilder
{
    private string? _message;
    private string? _authorName;
    private string? _authorEmail;
    private bool _allowEmpty;
    private bool _amend;
    private CommandOptions _options = new();

    public GitCommitBuilder WithMessage(string message) { _message = message; return this; }
    public GitCommitBuilder WithAuthor(string name, string email) { _authorName = name; _authorEmail = email; return this; }
    public GitCommitBuilder WithAllowEmpty() { _allowEmpty = true; return this; }
    public GitCommitBuilder WithAmend() { _amend = true; return this; }
    
    public CommandResult Build()
    {
        var args = new List<string> { "commit" };
        if (_message \!= null) { args.AddRange(new[] { "-m", _message }); }
        if (_authorName \!= null && _authorEmail \!= null) { args.AddRange(new[] { "--author", $"{_authorName} <{_authorEmail}>" }); }
        if (_allowEmpty) args.Add("--allow-empty");
        if (_amend) args.Add("--amend");
        
        return CommandExtensions.Run("git", args.ToArray(), _options);
    }
    
    public async Task<string> GetStringAsync(CancellationToken cancellationToken = default) => await Build().GetStringAsync(cancellationToken);
    public async Task<string[]> GetLinesAsync(CancellationToken cancellationToken = default) => await Build().GetLinesAsync(cancellationToken);
    public async Task ExecuteAsync(CancellationToken cancellationToken = default) => await Build().ExecuteAsync(cancellationToken);
}

Comparison with Existing Solutions

LibGit2Sharp vs Git CLI Wrapper

While LibGit2Sharp is the most popular .NET Git library, it:

  • Provides repository-level operations but not CLI command wrappers
  • Has different API patterns than TimeWarp.Cli's fluent style
  • Requires understanding of Git internals (objects, refs, etc.)
  • May not support all Git CLI features

TimeWarp.Cli's Git API would:

  • Provide a CLI wrapper that matches Git command syntax exactly
  • Follow TimeWarp.Cli's established fluent patterns
  • Maintain compatibility with all Git CLI features
  • Support existing Git configurations and aliases
  • Provide graceful error handling consistent with the library

Advantages of CLI Wrapper Approach

  1. Feature Completeness: Access to all Git CLI features without waiting for library updates
  2. Familiar Syntax: Maps directly to known Git commands
  3. Configuration Compatibility: Works with existing .gitconfig and Git aliases
  4. Tooling Integration: Compatible with existing Git hooks and tools
  5. Consistency: Matches TimeWarp.Cli's design philosophy

Benefits

For TimeWarp.Cli Users

  • Unified API Style: Consistent with existing DotNet commands
  • Type Safety: Compile-time validation of Git operations
  • IntelliSense Support: IDE autocomplete for Git options and parameters
  • Error Prevention: Reduces string-based command construction errors
  • Maintainability: Easier to refactor and update Git workflows

For C# Developers

  • Familiar Patterns: Uses established C# idioms and patterns
  • Async/Await Support: Modern async patterns for Git operations
  • Cancellation Support: Proper cancellation token support
  • Testing: Easier unit testing with typed commands

For DevOps and Automation

  • Script Safety: Compile-time validation prevents runtime failures
  • Refactoring Support: IDE refactoring tools work with typed APIs
  • Documentation: IntelliSense provides inline documentation
  • Consistency: Standardized API across different automation scripts

Implementation Considerations

Priority Commands (Phase 1)

Focus on the most commonly used Git commands:

  • Git.Status() - Repository status checking
  • Git.Add() - Staging files
  • Git.Commit() - Creating commits
  • Git.Push() - Pushing changes
  • Git.Pull() - Pulling changes
  • Git.Checkout() - Branch switching and creation
  • Git.Branch() - Branch management

Advanced Commands (Phase 2)

  • Git.Log() - Commit history
  • Git.Merge() - Branch merging
  • Git.Rebase() - Commit rewriting
  • Git.Remote() - Remote management
  • Git.Clone() - Repository cloning
  • Git.Config() - Git configuration
  • Git.Submodule() - Submodule management

Testing Strategy

Following TimeWarp.Cli's integration testing approach:

  • Real Git repository tests for command validation
  • Executable test scripts using TimeWarp.Cli itself
  • Tests for graceful error handling scenarios
  • Cross-platform compatibility tests

Documentation

  • Comprehensive XML documentation for IntelliSense
  • Example scripts demonstrating Git workflows
  • Migration guide from string-based commands
  • Integration examples with existing DotNet commands

Timeline Estimate

  • Phase 1 (Core Commands): 2-3 weeks
  • Phase 2 (Advanced Commands): 2-3 weeks
  • Documentation & Examples: 1 week
  • Testing & Polish: 1 week

Total Estimated Timeline: 6-8 weeks

Conclusion

A strongly typed Git API would significantly enhance TimeWarp.Cli's value proposition by providing a complete, type-safe interface for Git operations. This feature would complement the existing DotNet command builders perfectly and establish TimeWarp.Cli as the premier CLI wrapper library for C# developers.

The implementation would follow established patterns, ensuring consistency with the existing codebase while providing immediate value to users who need reliable, maintainable Git automation in their C# applications.


This feature request was generated with analysis of the current TimeWarp.Cli architecture and research into existing C# Git libraries.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions