Public/Invoke-GitInit.ps1

<#
.SYNOPSIS
    Creates a new bare git repository from a name.
.DESCRIPTION
    Initialises a new bare repo at <Destination>/<Name>.git, makes an initial
    empty commit on the default branch (respecting init.defaultBranch config,
    preferring 'master'), and sets up the same recommended git config that
    Invoke-GitCloneBare uses.
 
    If -RemoteUrl is provided the initial commit is pushed there and the bare
    repo is cloned from it, so origin is configured correctly. Create the
    repository on your hosting provider (GitHub, Azure DevOps, Bitbucket, etc.)
    first, then supply its URL here. Omit -RemoteUrl for a local-only bare repo.
.EXAMPLE
    Invoke-GitInit myrepo
    # Local bare repo only — no remote configured.
.EXAMPLE
    Invoke-GitInit myrepo -RemoteUrl https://github.com/user/myrepo -Worktrees master
    # Connects to an existing GitHub remote and adds a master worktree.
.EXAMPLE
    Invoke-GitInit myrepo -RemoteUrl https://dev.azure.com/org/project/_git/myrepo
    # Connects to an existing Azure DevOps remote.
.EXAMPLE
    gitt init myrepo -RemoteUrl https://github.com/user/myrepo
#>

function Invoke-GitInit {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Name,

        [Parameter()]
        [string] $Destination = (Get-Location).Path,

        # Remote repository URL to configure as origin.
        # Create the repo on your hosting provider first, then supply its URL here.
        # Omit this parameter for a local-only bare repo with no remote.
        [Parameter()]
        [string] $RemoteUrl,

        [Parameter()]
        [switch] $SkipSafeDirectory,

        [Parameter()]
        [switch] $SkipGitConfig,

        [Parameter()]
        [string[]] $Worktrees = @()
    )

    $bareDir = Join-Path $Destination "$Name.git"
    $tmpDir  = Join-Path ([System.IO.Path]::GetTempPath()) "gitt-init-$Name-$(Get-Random)"

    if ($PSCmdlet.ShouldProcess($bareDir, "Initialize bare repository '$Name'")) {
        # Determine desired default branch from global git config
        $defaultBranch = git config --global init.defaultBranch 2>$null
        if (-not $defaultBranch) { $defaultBranch = 'master' }

        # Bootstrap: create a temp non-bare repo with one empty commit so we have
        # a valid ref to clone from.
        Write-Verbose "Bootstrapping temporary repo at $tmpDir"
        git init $tmpDir
        if ($LASTEXITCODE -ne 0) { throw "git init failed for temporary repo at '$tmpDir'" }

        # Set desired initial branch via symbolic-ref (works on empty repos, unlike rev-parse)
        git -C $tmpDir symbolic-ref HEAD "refs/heads/$defaultBranch"

        git -C $tmpDir commit --allow-empty -m "Initial commit"
        if ($LASTEXITCODE -ne 0) {
            Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
            throw "Initial commit failed in temporary repo"
        }

        # Optionally push to a remote and clone from it so origin is wired up
        $cloneSource = $tmpDir
        if ($RemoteUrl) {
            Write-Verbose "Pushing initial commit to remote: $RemoteUrl"
            git -C $tmpDir remote add origin $RemoteUrl
            git -C $tmpDir push -u origin $defaultBranch
            if ($LASTEXITCODE -ne 0) {
                Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
                throw "Push to remote '$RemoteUrl' failed — ensure the repository exists and you have push access."
            }
            $cloneSource = $RemoteUrl
        }

        # Clone as bare from the remote (if provided) or the local temp repo
        Write-Verbose "Cloning bare repo: $cloneSource -> $bareDir"
        git clone --bare $cloneSource $bareDir
        if ($LASTEXITCODE -ne 0) {
            Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
            throw "git clone --bare failed from '$cloneSource'"
        }

        # Remove the auto-created origin (points to tmpDir or remote) and re-add
        # the real remote when one was provided, setting up proper fetch refspecs.
        git -C $bareDir remote remove origin 2>$null
        if ($RemoteUrl) {
            git -C $bareDir remote add origin $RemoteUrl
            git -C $bareDir config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
            git -C $bareDir fetch origin 2>$null
        }

        # Clean up temp repo
        Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue

        if (-not $SkipGitConfig) {
            Write-Verbose "Applying recommended git config"
            Set-BareRepoGitConfig -RepoPath $bareDir
        }

        if (-not $SkipSafeDirectory) {
            git config --global --add safe.directory $bareDir
        }

        # Create any requested worktrees
        Add-InitialWorktrees -RepoPath $bareDir -Worktrees $Worktrees `
            -Destination $Destination -SkipSafeDirectory:$SkipSafeDirectory

        Write-Host "Bare repo ready: $bareDir" -ForegroundColor Cyan
        return $bareDir
    }
}