git clone
的输出被写入到 stderr
中,如此处所述。我可以使用以下命令重定向它的输出:
git clone https://myrepo c:\repo 2>&1
但这将重定向所有输出,包括错误,从stderr
到stdout
。有没有一种方法可以将进度消息重定向到stdout
,但仍将错误消息写入stderr
。
git clone
的输出被写入到 stderr
中,如此处所述。我可以使用以下命令重定向它的输出:
git clone https://myrepo c:\repo 2>&1
但这将重定向所有输出,包括错误,从stderr
到stdout
。有没有一种方法可以将进度消息重定向到stdout
,但仍将错误消息写入stderr
。
明确了一个处理Git重定向的新方法, 通过MingW更新,自Git 2.15.x/2.16(2018年第一季度)开始提供。
参见提交b2f5571,提交1a172e4,提交3f94442(由Johannes Schindelin (dscho
)于2017年11月1日完成)。
(由Junio C Hamano -- gitster
--在提交421f21c中合并,2017年11月9日)
mingw
:添加重定向标准句柄的实验性功能特别是在从应用程序调用Git时,如Visual Studio的Team Explorer,关闭标准输入/输出/错误很重要。
然而,在Windows上生成进程时,如果要使用它们,则必须将这些句柄标记为可继承的,但该标志是全局标志,可能会被其他生成的进程使用,然后不知道关闭那些句柄。让我们引入一组环境变量(
GIT_REDIRECT_STDIN
等),指定文件路径,甚至更好的是命名管道(类似于Unix套接字),由生成的Git进程使用。
这有助于解决上述问题:这些命名管道将在启动时以非可继承方式打开,并且不会传递任何句柄(因此任何生成的子代都不需要关闭继承的句柄)。此功能已随Git for Windows一起发布(标记为实验性)自v2.11.0(2)以来,因此它已经进行了一些严格的测试。
Git文档现在包含:
GIT_REDIRECT_STDIN:
GIT_REDIRECT_STDOUT:
GIT_REDIRECT_STDERR:
仅限Windows:允许将标准输入/输出/错误句柄重定向到环境变量指定的路径。 这在多线程应用程序中非常有用,因为通过
CreateProcess()
传递标准句柄的规范方法不可行,因为它需要将句柄标记为可继承(因此每个衍生进程都会继承它们,可能会阻止常规的Git操作)。主要预期使用情况是使用命名管道进行通信(例如:
\\.\pipe\my-git-stdin-123
)。
并且还添加了:
mingw
:可选择通过相同的句柄重定向stderr/stdoutPowershell和Unix shell中的"
2>&1
"符号表示将stderr
重定向到已经写入stdout
的相同句柄中。让我们使用这个特殊值来实现相同的技巧,使用
GIT_REDIRECT_STDERR
和GIT_REDIRECT_STDOUT
:如果前者的值为2>&1
,则stderr
将简单地写入与stdout
相同的句柄。该功能是由Jeff Hostetler建议的。
用法示例:$env:GIT_REDIRECT_STDERR = '2>&1'
$env:GIT_REDIRECT_STDERR = '2>&1'
即可解决该问题。 - Oscar Vasquez<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages, and lastly the exit code
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
$exit = 0
$path = [System.IO.Path]::GetTempFileName()
Invoke-Expression "git $Command 2> $path"
$exit = $LASTEXITCODE
if ( $exit -gt 0 )
{
Write-Error (Get-Content $path).ToString()
}
else
{
Get-Content $path | Select-Object -First 1
}
$exit
}
catch
{
Write-Host "Error: $_`n$($_.ScriptStackTrace)"
}
finally
{
if ( Test-Path $path )
{
Remove-Item $path
}
}
}
通常来说:
控制台(终端)应用程序 - 无论是在Windows还是Unix-like平台上 - 只有两个输出流可用:
因此,不能且不应该通过stderr输出的存在来推断成功与失败。
相反,您必须仅依赖于应用程序的进程退出代码:
0
表示成功在PowerShell中,进程退出代码反映在自动变量$LASTEXITCODE
中。
具体来说,这意味着:
鉴于git
的stderr输出行,您无法推断它们代表错误消息还是其他类型的非数据信息,例如进度或状态消息,git
经常使用它们。
git
将其stderr输出分类重定向到stdout(通过将环境变量GIT_REDIRECT_STDERR
设置为字符串2>&1
; 在PowerShell中为$env:GIT_REDIRECT_STDERR = '2>&1'
)并不会有帮助,因为错误消息和进度/状态消息都会被发送到那里。如上所述,您应该只从非零退出代码中推断失败。
务实的方法是执行以下操作:
# Invoke git and capture both its stdout and stderr streams, as strings.
$result = git clone https://myrepo c:\repo 2>&1 | % ToString
# Throw an error, if git indicated failure.
if ($LASTEXITCODE) { Throw "git failed (exit code: $LASTEXITCODE):`n$($result -join "`n")" }
# Output the captured result, as an array of lines.
$result
注意:
| % ToString
(其中%
是缩写形式的ForEach-Object
)确保仅输出字符串,因为通过重定向到成功输出流(&1
)的stderr行(通过流2
)被包装在[System.Management.Automation.ErrorRecord]
实例中。
[System.Management.Automation.ErrorRecord]
也可以像字符串一样打印;相比之下,Windows PowerShell将它们打印为PowerShell错误。2>&1
在PowerShell版本7.1及以下版本中可能会产生意外的副作用-请参见此答案以获取背景信息。
更好地将外部程序调用集成到PowerShell的错误处理中是此RFC草案的主题,特别是具有在遇到非零退出代码时自动中止执行选项。
| % ToString
的位置,同时更新了解释。 - mklement0我想补充一下,如果你和我一样更关心的是在stderr中获取错误信息而不在意进度的话,那么有一个非常简单的解决方法——只需向命令添加--quiet(或-q)即可。
这会告诉git停止报告任何进度,除非出现实际的错误。
stderr
。
:
git clone https://myrepo c:\repo 2>$null
stderr
。我正在寻找的是一种将冗长的消息重定向到stdout
但保留stderr
中的错误的方法。在我的情况下,即使命令成功,输出也会进入stderr
。 - Pascal Bergerstderr
。
看起来这是一个git命令无法完成的任务。 - Oz Bar-Shalomstdin
、stdout
和stderr
。唯一的解决办法是使用其中许多库之一,这些库使用Git API并自己完成所有操作。 - Erisstderr
重定向到实际变量,如 $output
,并在每个 git
命令后调用 write-host
可以很有用,这样您仍然可以获得标准输出,同时修复 Windows 上错误的 stderr
输出。 - Matías Fidemraizer我已经修改了Invoke-Git,以更好地适应我的需求。
从我寻找解决方案时阅读的许多帖子中,我猜想有更多的人可以使用这个。
享受吧。
这个版本将会:
<#
.Synopsis
Invoke git, handling its quirky stderr that isn't error
.Outputs
Git messages
.Example
Invoke-Git push
.Example
Invoke-Git "add ."
#>
function Invoke-Git
{
param(
[Parameter(Mandatory)]
[string] $Command )
try {
# I could do this in the main script just once, but then the caller would have to know to do that
# in every script where they use this function.
$old_env = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Host -ForegroundColor Green "`nExecuting: git $Command "
$output = Invoke-Expression "git $Command "
if ( $LASTEXITCODE -gt 0 )
{
# note: No catch below (only the try/finally). Let the caller handle the exception.
Throw "Error Encountered executing: 'git $Command '"
}
else
{
# because $output probably has miultiple lines (array of strings), by piping it to write-host we get multiple lines.
$output | Write-Host
}
}
# note: No catch here. Let the caller handle it.
finally
{
$env:GIT_REDIRECT_STDERR = $old_env
}
}
Write-Error $output
的内容,并将 Write-Host 替换为 Write-Output,则可以很好地使用。 - stijn-C
一样,而不必 cd 进目录,还支持那些通常没有此功能的命令。所以你可以做 igit -dir some/path stash
。我主要在自动化脚本中使用它,否则就必须 cd 进目录,这很麻烦。igit checkout master
就像 igit 'checkout master'
。几乎是这样,因为标准的 PowerShell 警告适用:引号需要传递给底层命令,例如 igit log
'--format="%h %d"'。而且 PowerShell 不需要您输入完整的参数名称,这意味着
igit push -v将被解释为
igit push -Verbose而不是传递
-v,即向 git 发送详细推送。使用双破折号来处理它
igit -- push -v` 或在所有命令后添加引号。代码:
<#
.SYNOPSIS
Run git, Powershell-style.
.DESCRIPTION
By default some git commands (clone, checkout, ...) write a part of their
output to stderr, resulting in PS treating that as an error.
Here we work around that by redirecting stderr and using git's exit code
to check if something was actually wrong, and use Write-Error if that's the case,
i.e. standard PS error handling which works with -ErrorAction/-ErrorVariable etc.
The command can be passed as a string or as separate strings.
Additionally takes a $Directory argument which when used has the same effect as git -C,
but also works for clone/stash/submodule/... commands making it easier to automate those.
The $Git argument can be used to specify the executable.
.EXAMPLE
Invoke-Git status
Invoke-Git -Directory some/path status
Invoke-Git 'push -v'
Invoke-Git -Verbose -- push -v # Pass that last -v to git.
#>
function Invoke-Git {
[CmdletBinding()]
param(
[Parameter()] [Alias('Dir')] [String] $Directory = $null,
[Parameter()] [String] $Git = 'git',
[Parameter(Mandatory, Position=0, ValueFromRemainingArguments=$true)] [string] $Command
)
try {
$commandParts = $Command.Split(' ')
$subCommand = $commandParts[0]
if ($Directory -and $subCommand -eq 'clone') {
# To make all commands look alike handle this one as well.
$Command = ($commandParts + @($Directory)) -join ' '
} elseif ($Directory -and @('submodule', 'stash', 'init') -eq $subCommand) {
# These currently require one to be in the git directory so go there.
$currentDir = Get-Location
cd $Directory
} elseif ($Directory) {
if ($commandParts -eq '-C') {
# Not an error, git will pick the last one, but unexpected.
Write-Warning 'Better use either -Directory or -C, not both'
}
$Command = "-C $Directory " + $Command
}
Write-Verbose "Invoke-Git on '$Directory' with command '$Command'"
$gitRedirection = $env:GIT_REDIRECT_STDERR
$env:GIT_REDIRECT_STDERR = '2>&1'
# Deliberately not getting output here: while this means we cannot pass the actual error to Write-Error,
# it does result in all commands being shown 'live'. Otherwise when doing a clone for instance,
# nothing gets displayed while git is doing it's thing which is unexepected and too different from normal usage.
Invoke-Expression "$Git $Command"
if ($LASTEXITCODE -ne 0) {
Write-Error "git exited with code $LASTEXITCODE"
}
} finally {
$env:GIT_REDIRECT_STDERR = $gitRedirection
if ($currentDir) {
cd $currentDir
}
}
}
New-Alias -Name IGit -Value Invoke-Git -ErrorAction SilentlyContinue