|
35 | 35 | .NOTES |
36 | 36 | Use-Git will generate two events before git runs. They will have the source identifiers of "Use-Git" and "Use-Git $GitArgument" |
37 | 37 | #> |
38 | | - [Alias('git')] |
| 38 | + [Alias('git','realgit','gitreal')] |
39 | 39 | [CmdletBinding(PositionalBinding=$false,SupportsShouldProcess,ConfirmImpact='Low')] |
40 | 40 | param( |
41 | 41 | # Any arguments passed to git. All positional arguments will automatically be passed to -GitArgument. |
|
53 | 53 | ) |
54 | 54 |
|
55 | 55 | dynamicParam { |
| 56 | + # To get dynamic parameters, we need to look at our invocation |
56 | 57 | $myInv = $MyInvocation |
57 | 58 |
|
58 | | - $callstackPeek = @(Get-PSCallStack)[1] |
| 59 | + # and peek up the callstack. |
| 60 | + $callstackPeek = @(Get-PSCallStack)[-1] |
59 | 61 | $callingContext = |
60 | 62 | if ($callstackPeek.InvocationInfo.MyCommand.ScriptBlock) { |
61 | 63 | @($callstackPeek.InvocationInfo.MyCommand.ScriptBlock.Ast.FindAll({ |
|
66 | 68 | },$true))[0] |
67 | 69 | } |
68 | 70 |
|
| 71 | + # This will give us something to validate against, so we don't get dynamic parameters for everything |
69 | 72 | $ToValidate = |
70 | 73 | if (-not $callingContext -and |
71 | 74 | $callstackPeek.Command -like 'TabExpansion*' -and |
72 | 75 | $callstackPeek.InvocationInfo.BoundParameters.InputScript |
73 | 76 | ) { |
74 | 77 | $callstackPeek.InvocationInfo.BoundParameters.InputScript.ToString() |
75 | | - } else { |
| 78 | + } |
| 79 | + elseif ($callingContext) { |
76 | 80 | $callingContext.CommandElements -join ' ' |
77 | 81 | } |
| 82 | + elseif ($myInv.Line) { |
| 83 | + $myInv.Line.Substring($myInv.OffsetInLine - 1) |
| 84 | + } |
| 85 | + |
| 86 | + # If there's nothing to validate, there are no dynamic parameters. |
| 87 | + if (-not $ToValidate) { return } |
78 | 88 |
|
| 89 | + # Get dynamic parameters that are valid for this command |
79 | 90 | $dynamicParameterSplat = [Ordered]@{ |
80 | 91 | CommandName='Use-Git' |
81 | 92 | ValidateInput=$ToValidate |
82 | 93 | DynamicParameter=$true |
83 | 94 | DynamicParameterSetName='__AllParameterSets' |
84 | 95 | NoMandatoryDynamicParameter=$true |
| 96 | + } |
| 97 | + $myDynamicParameters = Get-UGitExtension @dynamicParameterSplat |
| 98 | + if (-not $myDynamicParameters) { return } |
| 99 | + |
| 100 | + # Here's where things get a little tricky. |
| 101 | + # we want to make as much muscle memory work as possible, so we don't wany any dynamic parameter that is not "fully" mapped. |
| 102 | + # So we need to walk over each command element. |
| 103 | + |
| 104 | + if (-not ($callingContext -or ($callstackPeek.Command -like 'TabExpansion*'))) { |
| 105 | + # (bonus points - within Pester, we cannot callstack peek effectively, and need to use the invocation line) |
| 106 | + # Therefore, when testing dynamic parameters, assign to a variable (because parenthesis and pipes may make this an invalid ScriptBlock) |
| 107 | + $callingContext = try { |
| 108 | + [scriptblock]::Create($ToValidate).Ast.EndBlock.Statements[0].PipelineElements[0] |
| 109 | + } catch { $null} |
85 | 110 | } |
86 | | - Get-UGitExtension @dynamicParameterSplat |
| 111 | + foreach ($commandElement in $callingContext.CommandElements) { |
| 112 | + if (-not $commandElement.parameterName) { continue } # that is a Powershell parameter |
| 113 | + foreach ($dynamicParam in @($myDynamicParameters.Values)) { |
| 114 | + if ( |
| 115 | + ( |
| 116 | + # If it started with this name |
| 117 | + $dynamicParam.Name.StartsWith($commandElement.parameterName, 'CurrentCultureIgnoreCase') -and |
| 118 | + # but was not the full parameter name, we'll remove it |
| 119 | + $dynamicParam.Name -ne $commandElement.parameterName |
| 120 | + ) -or # otherwise |
| 121 | + ( |
| 122 | + # If the dynamic parameter had aliases |
| 123 | + $dynamicParam.Attributes.AliasNames -and |
| 124 | + $(foreach ($aliasName in $dynamicParam.Attributes.AliasNames) { |
| 125 | + if (-not $aliasName) { continue } |
| 126 | + # and any of those aliases starts with the parameter name |
| 127 | + if ($aliasName.StartsWith($commandElement.parameterName, 'CurrentCultureIgnoreCase') -and |
| 128 | + # and is not the full parameter name |
| 129 | + $aliasName -ne $commandElement.parameterName) { |
| 130 | + # we also want to remove it |
| 131 | + $true; break |
| 132 | + } |
| 133 | + }) |
| 134 | + ) |
| 135 | + ) { |
| 136 | + $null = $myDynamicParameters.Remove($dynamicParam.Name) |
| 137 | + } |
| 138 | + } |
| 139 | + } |
| 140 | + $myDynamicParameters |
87 | 141 | } |
88 | 142 |
|
89 | 143 | begin { |
| 144 | + $myInv = $MyInvocation |
90 | 145 | if (-not $script:CachedGitCmd) { # If we haven't cahced the git command |
91 | 146 | # go get it. |
92 | 147 | $script:CachedGitCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand('git', 'Application') |
|
117 | 172 | $gitArgsArray.AddRange($GitArgument) |
118 | 173 | } |
119 | 174 |
|
120 | | - foreach ($commandElement in $callingContext.CommandElements) { |
121 | | - if ($commandElement.parameterName -in 'd', 'v', 'c') { |
| 175 | + :nextCommandElement foreach ($commandElement in $callingContext.CommandElements) { |
| 176 | + if (-not $commandElement.parameterName) { $argumentNumber++; continue } |
| 177 | + $paramName = $commandElement.parameterName |
| 178 | + if ($paramName -in 'd', 'c', 'v') { |
122 | 179 | # Insert the argument into the list |
123 | 180 | $gitArgsArray.Insert( |
124 | | - $argumentNumber - 1, # ( don't forget to subtract one, because the command is an element) |
| 181 | + $argumentNumber - 1, # ( don't forget to subtract one, because the command name is an element) |
125 | 182 | $commandElement.Extent.ToString() |
126 | 183 | ) |
127 | | - if ($commandElement.parameterName -in 'd', 'c', 'v') { |
128 | | - if ($commandElement.parameterName -eq 'c') { |
129 | | - $ConfirmPreference = 'none' # so set confirm preference to none |
130 | | - } |
131 | | - $VerbosePreference = 'silentlyContinue' |
132 | | - $DebugPreference = 'silentlyContinue' |
| 184 | + if ($paramName -eq 'c') { |
| 185 | + $ConfirmPreference = 'none' # so set confirm preference to none |
133 | 186 | } |
| 187 | + $VerbosePreference = 'silentlyContinue' |
| 188 | + $DebugPreference = 'silentlyContinue' |
134 | 189 | } |
| 190 | + |
135 | 191 | $argumentNumber++ |
136 | 192 | } |
137 | 193 |
|
|
166 | 222 | return # we're done. |
167 | 223 | } |
168 | 224 |
|
| 225 | + $pipedInDirectories = $false |
| 226 | + |
169 | 227 | $inputObject = |
170 | 228 | @(foreach ($in in $AllInputObjects) { |
171 | 229 | if ($in -is [IO.FileInfo]) { # If the input is a file |
|
184 | 242 | $in.Fullname # and adding this file name. |
185 | 243 | } |
186 | 244 | } elseif ($in -is [IO.DirectoryInfo]) { |
| 245 | + $pipedInDirectories = $true |
187 | 246 | $directories += Get-Item -LiteralPath $in.Fullname # If the input was a directory, keep track of it. |
188 | 247 | } else { |
189 | 248 | # Otherwise, see if it was a path and it was a directory |
190 | 249 | if ((Test-Path $in -ErrorAction SilentlyContinue) -and |
191 | 250 | (Get-Item $in -ErrorAction SilentlyContinue) -is [IO.DirectoryInfo]) { |
| 251 | + $pipedInDirectories = $true |
192 | 252 | $directories += Get-Item $in |
193 | 253 | } else { |
194 | 254 |
|
|
267 | 327 |
|
268 | 328 | # Walk over each input for each directory |
269 | 329 | :nextInput foreach ($inObject in $InputDirectories[$dir]) { |
270 | | - # Continue if there was no input and we were not the first step of the pipeline. |
271 | | - if (-not $inObject -and $myInv.PipelinePosition -gt 1) { continue } |
| 330 | + # Continue if there was no input and we were not the first step of the pipeline that was not a directory. |
| 331 | + if (-not $inObject -and ( |
| 332 | + $myInv.PipelinePosition -gt 1 |
| 333 | + ) -and -not $pipedInDirectories) { continue } |
272 | 334 |
|
273 | 335 | $AllGitArgs = @(@($GitArgument) + $inObject) # Then we collect the combined arguments |
274 | 336 |
|
|
344 | 406 | Write-Progress -PercentComplete $percentageComplete -Status "git $allGitArgs " -Activity "$($dir) " -Id $progId |
345 | 407 | } |
346 | 408 |
|
| 409 | + # If -WhatIf was passed, $WhatIfPreference will be true. |
347 | 410 | if ($WhatIfPreference) { |
| 411 | + # If that's the case, return the command line we would execute. |
348 | 412 | "git $AllGitArgs" |
349 | 413 | } |
350 | 414 |
|
351 | | - # If we have indicated we do not care about -Confirmation, don't prompt |
| 415 | + # otherwise, if we have indicated we do not want to -Confirm, don't prompt. |
352 | 416 | elseif (($ConfirmPreference -eq 'None' -and (-not $paramCopy.Confirm)) -or |
353 | | - $PSCmdlet.ShouldProcess("$pwd : git $allGitArgs") # otherwise, as for confirmation to run. |
| 417 | + $PSCmdlet.ShouldProcess("$pwd : git $allGitArgs") # otherwise, prompt for confirmation to run. |
354 | 418 | ) { |
355 | 419 | $eventSourceIds = @("Use-Git","Use-Git $allGitArgs") |
356 | | - $messageData = @{ |
357 | | - GitRoot = "$pwd" |
| 420 | + $messageData = [Ordered]@{ |
358 | 421 | GitCommand = @(@("git") + $AllGitArgs) -join ' ' |
359 | | - } |
| 422 | + GitRoot = "$pwd" |
| 423 | + } |
| 424 | + |
360 | 425 | $null = |
361 | 426 | foreach ($sourceIdentifier in $eventSourceIds) { |
362 | 427 | New-Event -SourceIdentifier $sourceIdentifier -MessageData $messageData |
363 | 428 | } |
364 | | - & $script:CachedGitCmd @AllGitArgs *>&1 | # Then we run git, combining all streams into output. |
365 | | - # then pipe to Out-Git, which will |
366 | | - Out-Git @OutGitParams # output git as objects. |
367 | 429 |
|
368 | | - # These objects are decorated for the PowerShell Extended Type System. |
369 | | - # This makes them easy to extend and customize their display. |
370 | | - # If Out-Git finds one or more extensions to run, these can parse the output. |
| 430 | + if ($myInv.InvocationName -in 'realgit', 'gitreal') { |
| 431 | + & $script:CachedGitCmd @AllGitArgs *>&1 |
| 432 | + } else { |
| 433 | + & $script:CachedGitCmd @AllGitArgs *>&1 | # Then we run git, combining all streams into output. |
| 434 | + # then pipe to Out-Git, which will |
| 435 | + Out-Git @OutGitParams # output git as objects. |
| 436 | + |
| 437 | + # These objects are decorated for the PowerShell Extended Type System. |
| 438 | + # This makes them easy to extend and customize their display. |
| 439 | + # If Out-Git finds one or more extensions to run, these can parse the output. |
| 440 | + } |
371 | 441 | } |
372 | 442 |
|
373 | 443 | } |
|
0 commit comments