Complete/SubCommand/SparseCheckout.ps1
# Copyright (C) 2024 kzrnm # Based on git-completion.bash (https://github.com/git/git/blob/HEAD/contrib/completion/git-completion.bash). # Distributed under the GNU General Public License, version 2.0. using namespace System.Management.Automation; function Complete-GitSubCommand-sparse-checkout { [CmdletBinding(PositionalBinding = $false)] [OutputType([CompletionResult[]])] param( [CommandLineContext] [Parameter(Position = 0, Mandatory)]$Context ) [string] $Subcommand = $Context.SubcommandWithoutGlobalOption() [string] $Current = $Context.CurrentWord() $subcommands = gitResolveBuiltins $Context.Command if (!$subcommand) { if (!$Context.HasDoubledash()) { if ($Current -eq '-') { $script:__helpCompletion } else { $subcommands | gitcomp -Current $Current -DescriptionBuilder { switch ($_) { 'list' { 'describe the directories or patterns' } 'init' { 'like set with no specified paths (deprecated)' } 'set' { 'enable the necessary sparse-checkout config settings' } 'add' { 'update the sparse-checkout file to include additional directories' } 'reapply' { 'reapply the sparsity pattern rules to paths in the working tree' } 'disable' { 'disable the core.sparseCheckout config setting' } 'check-rules' { 'check whether sparsity rules match one or more paths' } } } } } return } if (!$Context.HasDoubledash()) { $shortOpts = Get-GitShortOptions $Context.Command $Subcommand -Current $Current if ($shortOpts) { return $shortOpts } if ($Current.StartsWith('--')) { gitCompleteResolveBuiltins $Context.Command $Subcommand -Current $Current return } } if ($Subcommand -cin 'add', 'set') { if (usingCone $Context) { sparseCheckoutDirectories $Current } else { sparseCheckoutSlashLeadingPaths $Current } } } # __gitcomp_directories function sparseCheckoutDirectories { [CmdletBinding()] [OutputType([CompletionResult[]])] param ( [Parameter(Mandatory, Position = 0)] [AllowEmptyString()] [string] $Current ) if ($Current -cmatch '^.*/') { $dir = $Matches[0] } else { $dir = './' } @(__git ls-tree -z '-d' --name-only HEAD "$dir") -split '\0' | Where-Object { $_ -and "$_".StartsWith($Current) } | ForEach-Object { [CompletionResult]::new( ("$_/" | escapeSpecialChar), "$_/", 'ProviderItem', "$_/" ) } } # __gitcomp_slash_leading_paths function sparseCheckoutSlashLeadingPaths { [CmdletBinding()] [OutputType([CompletionResult[]])] param ( [Parameter(Mandatory, Position = 0)] [AllowEmptyString()] [string] $Current ) # Since we are dealing with a sparse-checkout, subdirectories may not # exist in the local working copy. Therefore, we want to run all # ls-files commands relative to the repository toplevel. $toplevel = "$(__git rev-parse --show-toplevel)/" # If the paths provided by the user already start with '/', then # they are considered relative to the toplevel of the repository # already. If they do not start with /, then we need to adjust # them to start with the appropriate prefix. if ($Current.StartsWith('/')) { $Current = $Current.Substring(1) $Prefix = '' } else { [string]$Prefix = (__git rev-parse --show-prefix) } # Since sparse-index is limited to cone-mode, in non-cone-mode the # list of valid paths is precisely the cached files in the index. # # NEEDSWORK: # 1) We probably need to take care of cases where ls-files # responds with special quoting. # 2) We probably need to take care of cases where ${cur} has # some kind of special quoting. # 3) On top of any quoting from 1 & 2, we have to provide an extra # level of quoting for any paths that contain a '*', '?', '\', # '[', ']', or leading '#' or '!' since those will be # interpreted by sparse-checkout as something other than a # literal path character. # Since there are two types of quoting here, this might get really # complex. For now, just punt on all of this... @(__git -C "$toplevel" ls-files -z --cached -- "${Prefix}${Current}*") -split '\0' | Where-Object { $_ } | ForEach-Object { [CompletionResult]::new( ("/$_" | escapeSpecialChar), "/$_", 'ProviderItem', "/$_" ) } } function usingCone { [OutputType([bool])] param ( [CommandLineContext] [Parameter(Position = 0, Mandatory)]$Context ) $hasConeOption = $false for ($i = $Context.DoubledashIndex - 1; $i -gt $Context.CommandIndex; $i--) { if ($Context.Words[$i] -ceq '--no-cone') { return $false } if ($Context.Words[$i] -ceq '--cone') { $hasConeOption = $true } } if ( ((__git config get core.sparseCheckout) -ceq 'true') -and ((__git config core.sparseCheckoutCone) -ceq 'false') -and !$hasConeOption ) { return $false } return $true } |