Set-Repository.ps1

function Get-LocalRepositoryDirectory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Root,
        [string]$Org
    )

    # Enumerate names instead of constructing wildcard paths. Do not follow
    # directory links while discovering repositories owned by this root.
    foreach ($owner in Get-ChildItem -LiteralPath $Root -Directory -Force -ErrorAction Stop) {
        if ($owner.Name -eq '.git' -or ($owner.Attributes -band [IO.FileAttributes]::ReparsePoint)) { continue }
        if ($Org -and $owner.Name -cne $Org) { continue }
        foreach ($repository in Get-ChildItem -LiteralPath $owner.FullName -Directory -Force -ErrorAction Stop) {
            if ($repository.Name -eq '.git' -or ($repository.Attributes -band [IO.FileAttributes]::ReparsePoint)) { continue }
            [pscustomobject]@{ Org = $owner.Name; Name = $repository.Name; Path = $repository.FullName }
        }
    }
}

function Get-LocalRepositoryWorkingTree {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Path)

    # Ambient repository overrides must not redirect validation to some other
    # repository. Overrides apply only to these read-only child processes.
    $environment = @{
        GIT_DIR = $null; GIT_WORK_TREE = $null; GIT_COMMON_DIR = $null
        GIT_INDEX_FILE = $null; GIT_OBJECT_DIRECTORY = $null
        GIT_ALTERNATE_OBJECT_DIRECTORIES = $null; GIT_NAMESPACE = $null
        GIT_CEILING_DIRECTORIES = $null; GIT_DISCOVERY_ACROSS_FILESYSTEM = $null
    }
    $pending = [Collections.Generic.Stack[string]]::new()
    $pending.Push($Path)
    while ($pending.Count) {
        $directory = $pending.Pop()
        if (Test-Path -LiteralPath (Join-Path $directory '.git')) {
            $inside = Invoke-GitProcess -Arguments @(
                '-C', $directory, 'rev-parse', '--is-inside-work-tree', '--show-prefix'
            ) -Environment $environment
            # Require the actual root, not a subdirectory discovered through a
            # broken marker or through an enclosing repository.
            if ($inside.ExitCode -eq 0 -and
                $inside.StandardOutput.Replace("`r`n", "`n") -ceq "true`n`n") {
                $head = Invoke-GitProcess -Arguments @(
                    '-C', $directory, 'symbolic-ref', '--quiet', 'HEAD'
                ) -Environment $environment
                if ($head.ExitCode -notin 0, 1) { continue }
                $ref = $head.StandardOutput.TrimEnd("`r", "`n")
                $branch = if ($head.ExitCode -eq 0 -and $ref.StartsWith('refs/heads/', [StringComparison]::Ordinal)) {
                    $ref.Substring('refs/heads/'.Length)
                } else { $null }
                [pscustomobject]@{ Path = $directory; Branch = $branch }
            }
            # A working tree (even an invalid one) is a discovery boundary.
            continue
        }
        # A bare repository is not a working tree or a branch-folder container.
        if ((Test-Path -LiteralPath (Join-Path $directory 'HEAD') -PathType Leaf) -and
            (Test-Path -LiteralPath (Join-Path $directory 'objects') -PathType Container)) { continue }
        foreach ($child in Get-ChildItem -LiteralPath $directory -Directory -Force -ErrorAction Stop) {
            if ($child.Attributes -band [IO.FileAttributes]::ReparsePoint) { continue }
            $pending.Push($child.FullName)
        }
    }
}

function Set-Repository {
    <#
    .SYNOPSIS
    Navigate to an existing local repository's working tree.
    .DESCRIPTION
    Resolves <root>/<org>/<repo> from any current directory. Only actual Git
    working-tree roots at or below that repository directory are eligible,
    including nested branch folders and detached worktrees. Directory links,
    bare repositories and .git contents are not searched. Registered worktrees
    outside that directory are not discovered; use Set-Worktree -Path for those.
 
    A sole valid working tree is selected automatically. Multiple repository
    names or working trees are errors: qualify the organization and choose an
    exact -Branch or -Worktree. No default branch is assumed. All validation is
    local and read-only; this command never clones, fetches, pulls, checks out a
    branch or creates worktrees. Failures and -WhatIf leave location unchanged.
    .PARAMETER Name
    Literal repository directory name, optionally qualified as org/name.
    Names are case-sensitive, even on Windows. Wildcards are not expanded.
    .PARAMETER Org
    Literal organization directory name. May not be combined with org/name.
    .PARAMETER Root
    Existing filesystem repository root. Defaults to $env:SOURCE_REPOS.
    An explicitly supplied root always takes precedence, including invalid roots.
    .PARAMETER Branch
    Exact, case-sensitive checked-out local branch name, not a folder name.
    Detached trees must be selected by Worktree. Does not switch branches.
    .PARAMETER Worktree
    Exact working-tree root, either absolute or relative to <root>/<org>/<repo>.
    Must match a discovered candidate; subdirectories and outside paths fail.
    .EXAMPLE
    Set-Repository terminal -Org microsoft
    Enters the sole valid working tree of $env:SOURCE_REPOS/microsoft/terminal.
    .EXAMPLE
    Set-Repository dotnet/runtime -Branch release/10.0
    Enters the existing tree whose checked-out branch is release/10.0.
    .EXAMPLE
    Set-Repository 'my repo' -Org 'my org' -Root '/src/repos' -Worktree 'topic folder' -WhatIf
    Previews navigation to an exact local working tree without changing location.
    .EXAMPLE
    Set-Repository terminal -Org microsoft -Worktree 'main' -Confirm
    Requests native confirmation before entering that existing working tree.
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Automatic')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [ValidateNotNullOrEmpty()]
        [string]$Org,

        [ValidateNotNullOrEmpty()]
        [string]$Root,

        [Parameter(Mandatory, ParameterSetName = 'Branch')]
        [ValidateNotNullOrEmpty()]
        [string]$Branch,

        [Parameter(Mandatory, ParameterSetName = 'Worktree')]
        [ValidateNotNullOrEmpty()]
        [string]$Worktree
    )

    if ($Name.Contains('/')) {
        $parts = $Name.Split('/')
        if ($Org -or $parts.Count -ne 2) {
            Write-Error 'Use either org/name or -Name with -Org, not both.'
            return
        }
        $Org, $Name = $parts
    }
    $segments = @($Name; if ($Org) { $Org })
    foreach ($segment in $segments) {
        if (-not $segment -or $segment -in '.', '..' -or $segment.IndexOfAny([char[]]"/\`0") -ge 0) {
            Write-Error 'Repository and organization names must be literal directory names, not paths.'
            return
        }
    }
    $reposRoot = if ($PSBoundParameters.ContainsKey('Root')) { $Root } else { $env:SOURCE_REPOS }
    if (-not $reposRoot) {
        Write-Error 'No repository root specified. Pass -Root or set $env:SOURCE_REPOS.'
        return
    }
    $resolved = Resolve-Path -LiteralPath $reposRoot -ErrorAction Ignore
    if (-not $resolved -or $resolved.Provider.Name -ne 'FileSystem' -or
        -not (Test-Path -LiteralPath $resolved.ProviderPath -PathType Container)) {
        Write-Error "Repository root must be an existing FileSystem directory: '$reposRoot'."
        return
    }
    $repositories = @(Get-LocalRepositoryDirectory -Root $resolved.ProviderPath -Org $Org -ErrorAction Stop |
        Where-Object Name -CEQ $Name)
    if ($repositories.Count -eq 0) {
        Write-Error "Repository '$Name' was not found under '$reposRoot' (organization '$Org')."
        return
    }
    if ($repositories.Count -gt 1) {
        Write-Error "Repository name '$Name' is ambiguous: $(($repositories | ForEach-Object { "$($_.Org)/$($_.Name)" }) -join ', '). Specify -Org."
        return
    }
    $container = $repositories[0].Path
    $targets = @(Get-LocalRepositoryWorkingTree -Path $container -ErrorAction Stop)
    if ($PSCmdlet.ParameterSetName -eq 'Branch') {
        $targets = @($targets | Where-Object Branch -CEQ $Branch)
    } elseif ($PSCmdlet.ParameterSetName -eq 'Worktree') {
        $targetPath = if ([IO.Path]::IsPathRooted($Worktree)) { $Worktree } else { Join-Path $container $Worktree }
        $targets = @($targets | Where-Object { Test-WorktreePathEquals -Left $_.Path -Right $targetPath })
    }
    if ($targets.Count -eq 0) {
        Write-Error "No valid Git working tree matches repository '$($repositories[0].Org)/$Name' and the supplied selection."
        return
    }
    if ($targets.Count -gt 1) {
        Write-Error "Working tree selection is ambiguous: $(($targets.Path | Sort-Object) -join ', '). Specify an exact -Branch or -Worktree."
        return
    }
    if ($PSCmdlet.ShouldProcess($targets[0].Path, 'Set current location')) {
        Set-Location -LiteralPath $targets[0].Path -ErrorAction Stop
    }
}

function Get-LocalRepositoryCompletion {
    [CmdletBinding()]
    param(
        [string]$ParameterName,
        [string]$WordToComplete,
        [System.Management.Automation.Language.CommandAst]$CommandAst,
        [System.Collections.IDictionary]$BoundParameters
    )

    try {
        $binding = [System.Management.Automation.Language.StaticParameterBinder]::BindCommand($CommandAst)
        foreach ($failure in $binding.BindingExceptions.Values) {
            # A trailing -Name/-Org is precisely where empty-prefix completion
            # is needed. Other binding errors must still fail closed.
            if ($failure.BindingException.ErrorId -ne 'MissingArgument' -or
                $failure.BindingException.ParameterName -ne $ParameterName) { return }
        }
        foreach ($parameter in 'Root', 'Org') {
            if ($binding.BoundParameters.ContainsKey($parameter) -and
                (-not $BoundParameters.Contains($parameter) -or
                 $BoundParameters[$parameter] -isnot [string] -or
                 -not $BoundParameters[$parameter])) { return }
        }
        $root = if ($BoundParameters.Contains('Root')) { $BoundParameters['Root'] } else { $env:SOURCE_REPOS }
        if (-not $root) { return }
        $resolved = Resolve-Path -LiteralPath $root -ErrorAction Ignore
        if (-not $resolved -or $resolved.Provider.Name -ne 'FileSystem' -or
            -not (Test-Path -LiteralPath $resolved.ProviderPath -PathType Container)) { return }
        $prefix = $WordToComplete
        if ($prefix.StartsWith("'")) { $prefix = $prefix.Trim("'").Replace("''", "'") }
        elseif ($prefix.StartsWith('"')) { $prefix = $prefix.Trim('"') }
        $org = $BoundParameters['Org']
        if ($ParameterName -eq 'Org') {
            $values = @(Get-ChildItem -LiteralPath $resolved.ProviderPath -Directory -Force -ErrorAction Stop |
                Where-Object { $_.Name -ne '.git' -and -not ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) } |
                Select-Object -ExpandProperty Name)
        } else {
            $qualified = $prefix.Contains('/')
            if ($qualified) {
                if ($org -or $prefix.Split('/').Count -ne 2) { return }
                $org = $prefix.Split('/')[0]
            }
            $values = @(Get-LocalRepositoryDirectory -Root $resolved.ProviderPath -Org $org -ErrorAction Stop |
                ForEach-Object { if ($qualified) { "$($_.Org)/$($_.Name)" } else { $_.Name } })
        }
        foreach ($value in $values | Sort-Object -Unique -CaseSensitive) {
            if ($value.StartsWith($prefix, [StringComparison]::Ordinal)) {
                [System.Management.Automation.CompletionResult]::new(
                    "'" + $value.Replace("'", "''") + "'", $value, 'ParameterValue', $value)
            }
        }
    } catch {
        # Completion is best-effort, but never fall back to another root/org.
        return
    }
}