Extensions/Git.Commit.Input.UGit.Extension.ps1

<#
.SYNOPSIS
    Git Commit Input
.DESCRIPTION
    Makes Git Commit easier to use from PowerShell by providing parameters for the -Message, -Title, -Body, and -Trailers
.EXAMPLE
    git commit -Title "Fixing Something"
.EXAMPLE
    git commit -Title "Changing Stuff" -Trailers @{"Co-Authored-By"="SOMEONE ELSE <Someone@Else.com>"}
#>

[ValidatePattern('^git commit')]
[Management.Automation.Cmdlet('Use','Git')]
[CmdletBinding(PositionalBinding=$false)]
param(
# The title of the commit. If -Message is also provided, this will become part of the -Body
[Alias('Subject')]
[string]
$Title,

# The commit message.
[string]
$Message,

# The type of the commit. This uses the conventional commits format.
# https://www.conventionalcommits.org/en/v1.0.0/#specification
[ArgumentCompleter({
    param ( $commandName,
    $parameterName,
    $wordToComplete,
    $commandAst,
    $fakeBoundParameters )
    if ($wordToComplete) {
        @($ugit.ConventionalCommits.Types) -like "$WordToComplete*" -replace '^', "'" -replace '$',"'"
    } else {
        $ugit.ConventionalCommits.Types -replace '^', "'" -replace '$',"'"
    }
})]
[string]
$Type,

# The scope of the commit. This uses the conventional commits format.
# https://www.conventionalcommits.org/en/v1.0.0/#specification
[string]
$Scope,

# A description of the commit. This uses the conventional commits format.
# https://www.conventionalcommits.org/en/v1.0.0/#specification
[string]
$Description,

# The footer for the commit. This uses the conventional commits format.
# https://www.conventionalcommits.org/en/v1.0.0/#specification
[string]
$Footer,

# The body of the commit.
[string]
$Body,

# Any git trailers to add to the commit.
# git trailers are key-value pairs you can use to associate metadata with a commit.
# As this uses --trailer, this requires git version 2.33 or greater.
[Alias('Trailer','CommitMetadata','GitMetadata')]
[Collections.IDictionary]
$Trailers,

# If set, will amend an existing commit.
[switch]
$Amend,

# The commit date.
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Date','Time','DateTime','Timestamp')]
[datetime]
$CommitDate,

# If provided, will mark this commit as a fix.
# This will add 'Fixes #...' to your commit message.
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Fixes','Fixed')]
[string[]]
$Fix,

# If provided, will mark this commit as a close.
# This will add 'Closes #...' to your commit message.
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Closed','Closes')]
[string[]]
$Close,

# If provided, will mark this commit as a resolution.
# This will add 'Resolves #...' to your commit message.
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Resolves','Resolved')]
[string[]]
$Resolve,

# If provided, will mark this commit as referencing an issue.
# This will add 'Re #...' to your commit message.
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Re','Regard','Regards','Regarding','References')]
[string[]]
$Reference
)

$MyParameters =  [Ordered]@{} + $PSBoundParameters

# git commit -m can accept multiple messages, but the first message is somewhat special.
# (trailers cannot exist in the first message, and it's considered the subject by many other parts of git)

# So we want several potential things to become "-m", and we have to do this in the right order.

$Fixes = @(
    $IssuePattern = '^\#?\d+$'
    $IssueReplace = "^\#?"
    if ($Close) {
        $Close -match $IssuePattern -replace $IssueReplace, "Closes #"
    }
    if ($Fix) {
        $Fix -match $IssuePattern -replace $IssueReplace, "Fixes #"
    }
    if ($Resolve) {
        $Resolve -match $IssuePattern -replace $IssueReplace, "Resolves #"
    }
    if ($Reference) {
        $Reference -match $IssuePattern -replace $IssueReplace, "re #"
    }
)  -join ', '

# First up is Convential Commits
if ($type) # (if -Type was provided)
{
    if (-not $Description) {
        if ($Title) {
            $Description = $title
            $title = ''
        }
        elseif ($Message) {
            $Description = $Message
            $Message = ''
        }
        elseif ($body) {
            $Description = $Body
            $Message = ''
        }
    }
    "-m"
    # construct a conventional commit message.
    "${type}$(if ($scope) { "($scope)" }): $Description$(if ($Fixes) { " ( $fixes )"})" 
}

# If title was provided, pass it as a message
elseif ($Title) {
    if ($Fix) {
        if ($Title) {"-m";"$title$(if ($Fixes) { " ( $fixes )"})"}
    } else {
        if ($Title) {"-m";$title}
    }
}

# If -Message was provided, pass that as a message, too.
if ($Message) {
    "-m"
    $message
}

# If Body was provided, it counts as a message.
if ($Body) {"-m";$body}
elseif (
    # f someone passed description but not type, that should also count.
    $Description -and -not $type
) {
    "-m";$Description
}

if ($Footer) {
    "-m";$Footer
}


if ($Trailers) {    
    foreach ($kv in $Trailers.GetEnumerator()) {
        foreach ($val in $kv.Value) {
            "--trailer=$($kv.Key -replace ':','_colon_' -replace '\s', '-')=$val"
        }        
    }
}

if ($amend) {
    "--amend"   
}

if ($CommitDate) {
    "--date"        
    $CommitDate.ToString("o")
}