|
|
|
@ -14,7 +14,7 @@ Supports -WhatIf/-Confirm via ShouldProcess. |
|
|
|
|
|
|
|
|
|
|
|
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] |
|
|
|
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] |
|
|
|
param( |
|
|
|
param( |
|
|
|
[switch]$Force |
|
|
|
[switch]$Force |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
Set-StrictMode -Version Latest |
|
|
|
Set-StrictMode -Version Latest |
|
|
|
@ -22,130 +22,130 @@ $ErrorActionPreference = 'Stop' |
|
|
|
|
|
|
|
|
|
|
|
# --- File sets (ILSpy) ------------------------------------------------------ |
|
|
|
# --- File sets (ILSpy) ------------------------------------------------------ |
|
|
|
$Dotfiles = @( |
|
|
|
$Dotfiles = @( |
|
|
|
'.gitignore', '.editorconfig', '.gitattributes', '.gitmodules', |
|
|
|
'.gitignore', '.editorconfig', '.gitattributes', '.gitmodules', |
|
|
|
'.tgitconfig', '.vsconfig' |
|
|
|
'.tgitconfig', '.vsconfig' |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
$AllowedExts = @( |
|
|
|
$AllowedExts = @( |
|
|
|
'.bat','.config','.cs','.csproj','.css','.filelist','.fs','.html','.il', |
|
|
|
'.bat','.config','.cs','.csproj','.css','.filelist','.fs','.html','.il', |
|
|
|
'.ipynb','.js','.json','.less','.manifest','.md','.projitems','.props', |
|
|
|
'.ipynb','.js','.json','.less','.manifest','.md','.projitems','.props', |
|
|
|
'.ps1','.psd1','.ruleset','.shproj','.sln','.slnf','.svg','.template', |
|
|
|
'.ps1','.psd1','.ruleset','.shproj','.sln','.slnf','.svg','.template', |
|
|
|
'.tt', '.txt','.vb','.vsct','.vsixlangpack','.wxl','.xaml','.xml','.xshd','.yml' |
|
|
|
'.tt', '.txt','.vb','.vsct','.vsixlangpack','.wxl','.xaml','.xml','.xshd','.yml' |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
$IncludeNoExt = $true # include names like LICENSE |
|
|
|
$IncludeNoExt = $true # include names like LICENSE |
|
|
|
|
|
|
|
|
|
|
|
# --- Git checks / enumeration ----------------------------------------------- |
|
|
|
# --- Git checks / enumeration ----------------------------------------------- |
|
|
|
function Assert-InGitWorkTree { |
|
|
|
function Assert-InGitWorkTree { |
|
|
|
$inside = (& git rev-parse --is-inside-work-tree 2>$null).Trim() |
|
|
|
$inside = (& git rev-parse --is-inside-work-tree 2>$null).Trim() |
|
|
|
if ($LASTEXITCODE -ne 0 -or $inside -ne 'true') { |
|
|
|
if ($LASTEXITCODE -ne 0 -or $inside -ne 'true') { |
|
|
|
throw 'Not in a Git work tree.' |
|
|
|
throw 'Not in a Git work tree.' |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Assert-CleanWorkingTree { |
|
|
|
function Assert-CleanWorkingTree { |
|
|
|
if ($Force) { return } |
|
|
|
if ($Force) { return } |
|
|
|
|
|
|
|
|
|
|
|
$status = & git status --porcelain -z |
|
|
|
$status = & git status --porcelain -z |
|
|
|
if ($LASTEXITCODE -ne 0) { throw 'git status failed.' } |
|
|
|
if ($LASTEXITCODE -ne 0) { throw 'git status failed.' } |
|
|
|
|
|
|
|
|
|
|
|
if (-not [string]::IsNullOrEmpty($status)) { |
|
|
|
if (-not [string]::IsNullOrEmpty($status)) { |
|
|
|
throw 'Working tree not clean. Commit/stash changes or use -Force.' |
|
|
|
throw 'Working tree not clean. Commit/stash changes or use -Force.' |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Get-GitFilesUnderPwd { |
|
|
|
function Get-GitFilesUnderPwd { |
|
|
|
Assert-InGitWorkTree |
|
|
|
Assert-InGitWorkTree |
|
|
|
|
|
|
|
|
|
|
|
$repoRoot = (& git rev-parse --show-toplevel).Trim() |
|
|
|
$repoRoot = (& git rev-parse --show-toplevel).Trim() |
|
|
|
$pwdPath = (Get-Location).Path |
|
|
|
$pwdPath = (Get-Location).Path |
|
|
|
|
|
|
|
|
|
|
|
$tracked = & git -C $repoRoot ls-files -z |
|
|
|
$tracked = & git -C $repoRoot ls-files -z |
|
|
|
$others = & git -C $repoRoot ls-files --others --exclude-standard -z |
|
|
|
$others = & git -C $repoRoot ls-files --others --exclude-standard -z |
|
|
|
|
|
|
|
|
|
|
|
$allRel = ("$tracked$others").Split( |
|
|
|
$allRel = ("$tracked$others").Split( |
|
|
|
[char]0, [System.StringSplitOptions]::RemoveEmptyEntries) |
|
|
|
[char]0, [System.StringSplitOptions]::RemoveEmptyEntries) |
|
|
|
|
|
|
|
|
|
|
|
foreach ($relPath in $allRel) { |
|
|
|
foreach ($relPath in $allRel) { |
|
|
|
$fullPath = Join-Path $repoRoot $relPath |
|
|
|
$fullPath = Join-Path $repoRoot $relPath |
|
|
|
if ($fullPath.StartsWith($pwdPath, |
|
|
|
if ($fullPath.StartsWith($pwdPath, |
|
|
|
[System.StringComparison]::OrdinalIgnoreCase)) { |
|
|
|
[System.StringComparison]::OrdinalIgnoreCase)) { |
|
|
|
if (Test-Path -LiteralPath $fullPath -PathType Leaf) { |
|
|
|
if (Test-Path -LiteralPath $fullPath -PathType Leaf) { |
|
|
|
$fullPath |
|
|
|
$fullPath |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# --- Probes ----------------------------------------------------------------- |
|
|
|
# --- Probes ----------------------------------------------------------------- |
|
|
|
function Test-HasUtf8Bom { |
|
|
|
function Test-HasUtf8Bom { |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
$stream = [System.IO.File]::Open($Path,'Open','Read','ReadWrite') |
|
|
|
$stream = [System.IO.File]::Open($Path,'Open','Read','ReadWrite') |
|
|
|
try { |
|
|
|
try { |
|
|
|
if ($stream.Length -lt 3) { return $false } |
|
|
|
if ($stream.Length -lt 3) { return $false } |
|
|
|
|
|
|
|
|
|
|
|
$header = [byte[]]::new(3) |
|
|
|
$header = [byte[]]::new(3) |
|
|
|
[void]$stream.Read($header,0,3) |
|
|
|
[void]$stream.Read($header,0,3) |
|
|
|
|
|
|
|
|
|
|
|
return ($header[0] -eq 0xEF -and |
|
|
|
return ($header[0] -eq 0xEF -and |
|
|
|
$header[1] -eq 0xBB -and |
|
|
|
$header[1] -eq 0xBB -and |
|
|
|
$header[2] -eq 0xBF) |
|
|
|
$header[2] -eq 0xBF) |
|
|
|
} |
|
|
|
} |
|
|
|
finally { |
|
|
|
finally { |
|
|
|
$stream.Dispose() |
|
|
|
$stream.Dispose() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
catch { return $false } |
|
|
|
catch { return $false } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Test-ProbablyBinary { |
|
|
|
function Test-ProbablyBinary { |
|
|
|
# Binary if the first 8 KiB contains any NUL byte. |
|
|
|
# Binary if the first 8 KiB contains any NUL byte. |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
$stream = [System.IO.File]::Open($Path,'Open','Read','ReadWrite') |
|
|
|
$stream = [System.IO.File]::Open($Path,'Open','Read','ReadWrite') |
|
|
|
try { |
|
|
|
try { |
|
|
|
$len = [int][Math]::Min(8192,$stream.Length) |
|
|
|
$len = [int][Math]::Min(8192,$stream.Length) |
|
|
|
if ($len -le 0) { return $false } |
|
|
|
if ($len -le 0) { return $false } |
|
|
|
|
|
|
|
|
|
|
|
$buffer = [byte[]]::new($len) |
|
|
|
$buffer = [byte[]]::new($len) |
|
|
|
[void]$stream.Read($buffer,0,$len) |
|
|
|
[void]$stream.Read($buffer,0,$len) |
|
|
|
|
|
|
|
|
|
|
|
return ($buffer -contains 0) |
|
|
|
return ($buffer -contains 0) |
|
|
|
} |
|
|
|
} |
|
|
|
finally { |
|
|
|
finally { |
|
|
|
$stream.Dispose() |
|
|
|
$stream.Dispose() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
catch { return $false } |
|
|
|
catch { return $false } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# --- Mutation --------------------------------------------------------------- |
|
|
|
# --- Mutation --------------------------------------------------------------- |
|
|
|
function Remove-Utf8BomInPlace { |
|
|
|
function Remove-Utf8BomInPlace { |
|
|
|
# Write the existing buffer from offset 3, no extra full-size allocation. |
|
|
|
# Write the existing buffer from offset 3, no extra full-size allocation. |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
param([Parameter(Mandatory)][string]$Path) |
|
|
|
|
|
|
|
|
|
|
|
$bytes = [System.IO.File]::ReadAllBytes($Path) |
|
|
|
$bytes = [System.IO.File]::ReadAllBytes($Path) |
|
|
|
if ($bytes.Length -lt 3) { return $false } |
|
|
|
if ($bytes.Length -lt 3) { return $false } |
|
|
|
|
|
|
|
|
|
|
|
if ($bytes[0] -ne 0xEF -or |
|
|
|
if ($bytes[0] -ne 0xEF -or |
|
|
|
$bytes[1] -ne 0xBB -or |
|
|
|
$bytes[1] -ne 0xBB -or |
|
|
|
$bytes[2] -ne 0xBF) { |
|
|
|
$bytes[2] -ne 0xBF) { |
|
|
|
return $false |
|
|
|
return $false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$stream = [System.IO.File]::Open($Path,'Create','Write','ReadWrite') |
|
|
|
$stream = [System.IO.File]::Open($Path,'Create','Write','ReadWrite') |
|
|
|
try { |
|
|
|
try { |
|
|
|
$stream.Write($bytes, 3, $bytes.Length - 3) |
|
|
|
$stream.Write($bytes, 3, $bytes.Length - 3) |
|
|
|
$stream.SetLength($bytes.Length - 3) |
|
|
|
$stream.SetLength($bytes.Length - 3) |
|
|
|
} |
|
|
|
} |
|
|
|
finally { |
|
|
|
finally { |
|
|
|
$stream.Dispose() |
|
|
|
$stream.Dispose() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return $true |
|
|
|
return $true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
# --- Main ------------------------------------------------------------------- |
|
|
|
# --- Main ------------------------------------------------------------------- |
|
|
|
@ -155,16 +155,16 @@ Assert-CleanWorkingTree |
|
|
|
$allFiles = Get-GitFilesUnderPwd |
|
|
|
$allFiles = Get-GitFilesUnderPwd |
|
|
|
|
|
|
|
|
|
|
|
$targets = $allFiles | % { |
|
|
|
$targets = $allFiles | % { |
|
|
|
$fileName = [IO.Path]::GetFileName($_) |
|
|
|
$fileName = [IO.Path]::GetFileName($_) |
|
|
|
$ext = [IO.Path]::GetExtension($fileName) |
|
|
|
$ext = [IO.Path]::GetExtension($fileName) |
|
|
|
|
|
|
|
|
|
|
|
$isDot = $Dotfiles -contains $fileName |
|
|
|
$isDot = $Dotfiles -contains $fileName |
|
|
|
$isNoExt = -not $fileName.Contains('.') |
|
|
|
$isNoExt = -not $fileName.Contains('.') |
|
|
|
|
|
|
|
|
|
|
|
if ($isDot -or ($AllowedExts -contains $ext) -or |
|
|
|
if ($isDot -or ($AllowedExts -contains $ext) -or |
|
|
|
($IncludeNoExt -and $isNoExt -and -not $isDot)) { |
|
|
|
($IncludeNoExt -and $isNoExt -and -not $isDot)) { |
|
|
|
$_ |
|
|
|
$_ |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
| ? { Test-HasUtf8Bom $_ } |
|
|
|
| ? { Test-HasUtf8Bom $_ } |
|
|
|
| ? { -not (Test-ProbablyBinary $_) } |
|
|
|
| ? { -not (Test-ProbablyBinary $_) } |
|
|
|
@ -174,35 +174,35 @@ $byExtension = @{} |
|
|
|
$dotfileChanges = 0 |
|
|
|
$dotfileChanges = 0 |
|
|
|
|
|
|
|
|
|
|
|
$targets | % { |
|
|
|
$targets | % { |
|
|
|
$relative = Resolve-Path -LiteralPath $_ -Relative |
|
|
|
$relative = Resolve-Path -LiteralPath $_ -Relative |
|
|
|
|
|
|
|
|
|
|
|
if ($PSCmdlet.ShouldProcess($relative,'Strip UTF-8 BOM')) { |
|
|
|
if ($PSCmdlet.ShouldProcess($relative,'Strip UTF-8 BOM')) { |
|
|
|
if (Remove-Utf8BomInPlace -Path $_) { |
|
|
|
if (Remove-Utf8BomInPlace -Path $_) { |
|
|
|
$changed++ |
|
|
|
$changed++ |
|
|
|
|
|
|
|
|
|
|
|
$fileName = [IO.Path]::GetFileName($_) |
|
|
|
$fileName = [IO.Path]::GetFileName($_) |
|
|
|
if ($Dotfiles -contains $fileName) { $dotfileChanges++ } |
|
|
|
if ($Dotfiles -contains $fileName) { $dotfileChanges++ } |
|
|
|
|
|
|
|
|
|
|
|
$ext = [IO.Path]::GetExtension($fileName) |
|
|
|
$ext = [IO.Path]::GetExtension($fileName) |
|
|
|
if (-not $byExtension.ContainsKey($ext)) { $byExtension[$ext] = 0 } |
|
|
|
if (-not $byExtension.ContainsKey($ext)) { $byExtension[$ext] = 0 } |
|
|
|
$byExtension[$ext]++ |
|
|
|
$byExtension[$ext]++ |
|
|
|
|
|
|
|
|
|
|
|
"stripped BOM: $relative" |
|
|
|
"stripped BOM: $relative" |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
"Done. Stripped BOM from $changed file(s)." |
|
|
|
"Done. Stripped BOM from $changed file(s)." |
|
|
|
|
|
|
|
|
|
|
|
if ($byExtension.Keys.Count -gt 0) { |
|
|
|
if ($byExtension.Keys.Count -gt 0) { |
|
|
|
"" |
|
|
|
"" |
|
|
|
"By extension:" |
|
|
|
"By extension:" |
|
|
|
$byExtension.GetEnumerator() | Sort-Object Name | % { |
|
|
|
$byExtension.GetEnumerator() | Sort-Object Name | % { |
|
|
|
$key = if ([string]::IsNullOrEmpty($_.Name)) { '[noext]' } else { $_.Name } |
|
|
|
$key = if ([string]::IsNullOrEmpty($_.Name)) { '[noext]' } else { $_.Name } |
|
|
|
" {0}: {1}" -f $key, $_.Value |
|
|
|
" {0}: {1}" -f $key, $_.Value |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ($dotfileChanges -gt 0) { |
|
|
|
if ($dotfileChanges -gt 0) { |
|
|
|
" [dotfiles]: $dotfileChanges" |
|
|
|
" [dotfiles]: $dotfileChanges" |
|
|
|
} |
|
|
|
} |
|
|
|
|