Private/Functions/GitHelpers.ps1

function Get-GithubRemoteContext {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [string]
        [Parameter()]
        $Path = '.'
    )

    $Context = [PSCustomObject]@{
        Provider = ''
        Owner    = ''
        Repo     = ''
        Branch   = ''
    }
    if($(Get-Location).Provider.Name -ne 'FileSystem') {
        return $Context
    }

    Push-Location

    try {
        Set-Location $Path
        $GitDir = git rev-parse --git-dir 2>$null
        if ($GitDir -and (Test-Path -Path $GitDir)) {
            $OriginUrl = git config --get remote.origin.url
            if (-not $OriginUrl) {
                return $Context
            }

            # Parse owner/repo from remote URL
            # Supports: https://github.com/owner/repo.git
            # git@github.com:owner/repo.git
            if ($OriginUrl -match 'github\.com[:/](?<Owner>[^/]+)/(?<Repo>[^/]+?)(?:\.git)?$') {
                $Context.Provider = 'github'
                $Context.Owner = $Matches.Owner
                $Context.Repo = $Matches.Repo
            }
            else {
                # Non-Github remote -- still extract owner/repo for general use
                try {
                    $Uri = [Uri]::new($OriginUrl)
                    $Parts = $Uri.AbsolutePath.Trim('/').TrimEnd('.git') -split '/'
                    if ($Parts.Count -ge 2) {
                        $Context.Owner = $Parts[0..($Parts.Count - 2)] -join '/'
                        $Context.Repo = $Parts[-1]
                    }
                }
                catch {
                    if ($OriginUrl -match '@(?<Host>.*?)[:/](?<Path>.+?)(?:\.git)?$') {
                        $Parts = $Matches.Path -split '/'
                        if ($Parts.Count -ge 2) {
                            $Context.Owner = $Parts[0..($Parts.Count - 2)] -join '/'
                            $Context.Repo = $Parts[-1]
                        }
                    }
                }
            }

            $Ref = git status | Select-String "^HEAD detached at (?<sha>.{7,40})|^On branch (?<branch>.*)"
            if ($Ref -and $Ref.Matches) {
                if ($Ref.Matches[0].Groups["branch"].Success) {
                    $Context.Branch = $Ref.Matches[0].Groups["branch"].Value
                } else {
                    $Context.Branch = "Detached HEAD"
                }
            }
        }
    }
    finally {
        Pop-Location
    }

    $Context
}

function Resolve-GithubRepository {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Position=0)]
        [string]
        $Repository = '.'
    )

    if ($Repository -eq '.') {
        $Context = Get-GithubRemoteContext
        if (-not $Context.Owner -or -not $Context.Repo) {
            throw "Could not infer Github repository based on current directory ($(Get-Location))"
        }
        return "$($Context.Owner)/$($Context.Repo)"
    }

    if ($Repository -match '^[^/]+/[^/]+$') {
        return $Repository
    }

    if ($Repository -match '^\d+$') {
        $Repo = Invoke-GithubApi GET "repositories/$Repository"
        return $Repo.full_name
    }

    throw "Invalid repository format '$Repository'. Expected 'owner/repo', numeric ID, or '.' for current directory."
}