|
using namespace System.IO |
|
using namespace System.Collections.Generic |
|
|
|
# Write verbose messages on import |
|
if ((Get-PSCallStack)[1].Arguments -imatch 'Verbose=True') { $PSDefaultParameterValues['*:Verbose'] = $true } |
|
|
|
|
|
if ((Test-ApplicationExistsInPath -ApplicationName 'git') -eq $false) { |
|
Write-Verbose 'git does not exist in the Path. Skipping the import of git ProfileUtility commands.' |
|
# Do not export any module commands. |
|
Export-ModuleMember |
|
return |
|
} |
|
# https://github.com/PowerShell/PowerShell/issues/17730#issuecomment-1190678484 |
|
$ExportedMembers = [List[string]]::new() |
|
|
|
|
|
# Private function pulled from TM-ProfileUtility |
|
function Get-CurrentPath { |
|
<# |
|
.SYNOPSIS |
|
Returns the current location's provider path. |
|
|
|
.OUTPUTS |
|
Returns the ProviderPath except when the current location matches the start to a UNC path. |
|
In those cases the $executionContext.SessionState.Path.CurrentLocation.Path is returned instead. |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param() |
|
|
|
$ProviderPath = (Get-Location).ProviderPath |
|
$result = if ($ProviderPath -match '\\\\') { |
|
$executionContext.SessionState.Path.CurrentLocation.Path |
|
} else { |
|
$ProviderPath |
|
} |
|
|
|
return $result |
|
} |
|
|
|
|
|
class GitStatusInfo { |
|
<# |
|
.SYNOPSIS |
|
This class is used to encapsulate the current status information of a Git repository. |
|
|
|
.DESCRIPTION |
|
GitStatusInfo contains lists of files that have been updated, unchanged, stage, untracked, or ignored. |
|
It provides a static method, GetCurrentStatusInfo, which checks the status of the repository using the |
|
'git status' command and fills these lists accordingly. |
|
|
|
This class helps in managing and visualizing the status information of a Git repository. |
|
#> |
|
$Updated = [List[FileInfo]]::new() |
|
$Deleted = [List[FileInfo]]::new() |
|
$AddedStaged = [List[FileInfo]]::new() |
|
$ModifiedStaged = [List[FileInfo]]::new() |
|
$UnTracked = [List[FileInfo]]::new() |
|
$Ignored = [List[FileInfo]]::new() |
|
$Unchanged = [List[FileInfo]]::new() |
|
|
|
[GitStatusInfo] static GetCurrentStatusInfo([string]$gitPath = [string]::Empty) { |
|
function Get-FilePaths { |
|
[OutputType([void])] |
|
param( |
|
[Parameter(Mandatory)] |
|
[string]$Path, |
|
|
|
[Parameter(Mandatory)] |
|
[AllowEmptyCollection()] |
|
[List[FileInfo]]$Collection, |
|
|
|
[Parameter(Mandatory)] |
|
[string]$Type |
|
) |
|
|
|
if ([Directory]::Exists($Path)) { |
|
foreach ($File in (Get-ChildItem -Path $Path -Recurse -File)) { |
|
$Collection.Add($File) |
|
} |
|
} elseif ([File]::Exists($Path)) { |
|
$Collection.Add([FileInfo]::New($Path)) |
|
} else { |
|
Write-Warning "$Type Path '$Path' does not exist." |
|
} |
|
} |
|
|
|
$CurrentStatus = [GitStatusInfo]::New() |
|
|
|
if ([string]::IsNullOrWhiteSpace($GitPath)) { $GitPath = Get-GitPath } |
|
|
|
if ([string]::IsNullOrWhiteSpace($GitPath)) { |
|
Write-Warning 'The path heirarchy does not contain a git directory.' |
|
return $CurrentStatus |
|
} |
|
|
|
$GitPath = (Resolve-Path -Path $GitPath -ErrorAction Stop).Path |
|
Push-Location $GitPath |
|
|
|
try { |
|
foreach ($line in (git status --show-stash --ignored --porcelain)) { |
|
$StartSymbol = $Line.Substring(0, 2) |
|
$GitPorcelainPath = $Line.Substring(2).Trim().TrimStart('"').TrimEnd('"') |
|
$ItemPath = Join-Path -Path $GitPath -ChildPath $GitPorcelainPath |
|
$Item = Get-Item -Path $ItemPath -Force |
|
switch ($StartSymbol) { |
|
' M' { $CurrentStatus.Updated.Add($Item) } |
|
' D' { $CurrentStatus.Deleted.Add([FileInfo]::New($ItemPath)) } |
|
'A ' { $CurrentStatus.AddedStaged.Add($Item) } |
|
'M ' { $CurrentStatus.ModifiedStaged.Add($Item) } |
|
'??' { Get-FilePaths -Path $Item.FullName -Collection $CurrentStatus.UnTracked -Type 'UnTracked' } |
|
'!!' { Get-FilePaths -Path $Item.FullName -Collection $CurrentStatus.Ignored -Type 'Ignored' } |
|
default { Write-Warning "GitStatusInfo Unknown Element: '$StartSymbol'`nLine: $Line" } |
|
} |
|
} |
|
|
|
[string[]]$ChangeElements = @( |
|
$CurrentStatus.Updated.FullName; |
|
$CurrentStatus.Deleted.FullName; |
|
$CurrentStatus.AddedStaged.FullName; |
|
$CurrentStatus.ModifiedStaged.FullName; |
|
$CurrentStatus.UnTracked.FullName; |
|
$CurrentStatus.Ignored.FullName |
|
) |
|
|
|
foreach ($line in (git ls-files)) { |
|
$ItemPath = Join-Path -Path $GitPath -ChildPath $line |
|
if ($ChangeElements -notcontains $ItemPath) { |
|
$CurrentStatus.Unchanged.Add((Get-Item -Path $ItemPath -Force)) |
|
} |
|
} |
|
} finally { |
|
Pop-Location |
|
} |
|
|
|
return $CurrentStatus |
|
} |
|
} |
|
|
|
|
|
function Get-GitBranch { |
|
<# |
|
.SYNOPSIS |
|
Returns the current Git branch with a relevant symbol based on the branch name. |
|
|
|
.DESCRIPTION |
|
The function recursively searches for the .git directory in the current and parent directories and retrieves |
|
the current Git branch. Symbols are also appended to the string if the branch matches the following patterns |
|
🚀 for '/master' or '/main' |
|
🚧 for '/dev' |
|
|
|
.PARAMETER Path |
|
An optional parameter allowing you to specify the location of the git folder to retrieve a branch information for. |
|
|
|
.PARAMETER NoSymbol |
|
An optional parameter that removes all extra formatting (including the brackets and optional branch symbol) and |
|
only returns the branch name as defined by: git rev-parse --abbrev-ref --symbolic-full-name '@{u}' |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param( |
|
[Parameter(Mandatory = $false)] |
|
[Validation.ValidatePathExists('Folder')] |
|
[string]$Path = (Get-CurrentPath), |
|
|
|
[Parameter(Mandatory = $false)] |
|
[switch]$NoSymbol |
|
) |
|
|
|
$Result = [string]::Empty |
|
|
|
if ((Get-GitPath -Path $Path) -ne [string]::Empty) { |
|
Push-Location -Path $Path |
|
try { |
|
# need to do this so the stderr doesn't show up in $error |
|
$ErrorActionPreferenceOld = $ErrorActionPreference |
|
$ErrorActionPreference = 'Ignore' |
|
$branch = git rev-parse --abbrev-ref --symbolic-full-name '@{u}' |
|
$ErrorActionPreference = $ErrorActionPreferenceOld |
|
# handle case where branch is local |
|
if ($lastexitcode -ne 0 -or $null -eq $branch) { |
|
$branch = git rev-parse --abbrev-ref HEAD |
|
} |
|
if ($NoSymbol) { |
|
$Result = "$branch" |
|
} else { |
|
$branchSymbol = if (($branch -imatch '/master') -or ($branch -imatch '/main')) { |
|
'🚀' |
|
} elseif ($branch -imatch '/dev') { |
|
'🚧' |
|
} |
|
$Result = "[$branch$branchSymbol] " |
|
} |
|
} finally { |
|
Pop-Location |
|
} |
|
} |
|
return $Result |
|
} |
|
$ExportedMembers.Add('Get-GitBranch') |
|
|
|
|
|
function Get-GitPath { |
|
<# |
|
.SYNOPSIS |
|
Retrieves the path of the current Git repository if it exists in the current path hierarchy. |
|
|
|
.DESCRIPTION |
|
This function looks for the .git directory starting from the specified path and continuing up the directory |
|
tree. |
|
|
|
.PARAMETER Path |
|
The path to begin the search for the git repository in. By default this will look in the current working directory. |
|
|
|
.OUTPUTS |
|
Returns the path of the repository where the .git directory is located as a string. |
|
If no .git directory is found, it returns an empty string. |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([string])] |
|
param( |
|
[Parameter(Mandatory = $false)] |
|
[Validation.ValidatePathExists('Folder')] |
|
[string]$Path = (Get-CurrentPath) |
|
) |
|
|
|
$IteratePath = "$Path" |
|
|
|
while ([string]::IsNullOrWhiteSpace($IteratePath) -eq $false) { |
|
if ([Directory]::Exists((Join-Path -Path $IteratePath -ChildPath '.git'))) { |
|
return $IteratePath |
|
} |
|
$IteratePath = [Path]::GetDirectoryName($IteratePath) |
|
} |
|
return [string]::Empty |
|
} |
|
$ExportedMembers.Add('Get-GitPath') |
|
|
|
|
|
function Get-GitStatusInfo { |
|
<# |
|
.SYNOPSIS |
|
Retrieves the git status info for the specified git directory. |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([GitStatusInfo])] |
|
param( |
|
[Parameter(Mandatory = $false)] |
|
[string]$Path = [string]::Empty |
|
) |
|
|
|
[GitStatusInfo]::GetCurrentStatusInfo($Path) |
|
} |
|
$ExportedMembers.Add('Get-GitStatusInfo') |
|
|
|
|
|
function Redo-GitCommitAsSigned { |
|
<# |
|
.SYNOPSIS |
|
Modifies the last git commit to be signed. |
|
|
|
.DESCRIPTION |
|
This function allows you to sign the last commit by amending it with the -S flag. |
|
|
|
.EXAMPLE |
|
Redo-GitCommitAsSigned |
|
Signs the last commit in the current repository. |
|
#> |
|
[Alias('Sign-LastGitCommit')] |
|
[OutputType([Void])] |
|
param() |
|
|
|
git commit --amend --no-edit -n -S |
|
} |
|
$ExportedMembers.Add('Redo-GitCommitAsSigned') |
|
|
|
|
|
function Remove-GitTag { |
|
<# |
|
.SYNOPSIS |
|
Removes a specified git tag both locally and remotely. |
|
|
|
.PARAMETER Tag |
|
The name of the git tag to remove. |
|
|
|
.PARAMETER Remote |
|
Removes the tag from the origin location in addition to the removing the tag from the local repository. |
|
|
|
.EXAMPLE |
|
# Remove the 'v1.0.0' tag from the local and remote repositories. |
|
Remove-GitTag -Tag 'v1.0.0' -Remote |
|
#> |
|
[CmdletBinding()] |
|
param ( |
|
[Parameter(Mandatory)] |
|
[string]$Tag, |
|
|
|
[Parameter(Mandatory = $false)] |
|
[switch]$IncludeRemote |
|
) |
|
|
|
git tag -d $Tag |
|
if ($IncludeRemote) { |
|
git push --delete origin $Tag |
|
} |
|
} |
|
$ExportedMembers.Add('Remove-GitTag') |
|
|
|
|
|
function Set-GitFileAssumeUnchanged { |
|
<# |
|
.SYNOPSIS |
|
Tells git to assume a file in the Git repository is unchanged. |
|
|
|
.DESCRIPTION |
|
This function makes Git "assume" the file has not been changed, meaning that Git will ignore any changes to |
|
that file. The file will not appear in 'git status' and it will not be checked for changes. |
|
This is useful for files with local changes that are needed, but should not be committed. |
|
|
|
.PARAMETER FilePath |
|
The relative or absolute path to the file to mark as "assume unchanged". |
|
|
|
.PARAMETER GitPath |
|
The relative or absolute path to the file to mark as "assume unchanged". |
|
#> |
|
[Alias('Ignore-GitFileChanges')] |
|
[OutputType([Void])] |
|
param( |
|
[Parameter(Mandatory)] |
|
[Validation.ValidatePathExists('File')] |
|
[string]$FilePath |
|
) |
|
|
|
$Item = Get-Item -Path $FilePath -Force -ErrorAction Stop |
|
Push-Location (Get-GitPath -Path $Item.Parent) |
|
try { |
|
$StatusInfo = [GitStatusInfo]::GetCurrentStatusInfo() |
|
if ($StatusInfo.Staged.FullName -contains $Item.FullName) { |
|
# Reset the item so that it is no longer staged |
|
git reset HEAD -- $Item.FullName |
|
} |
|
git update-index --assume-unchanged $FilePath |
|
} finally { |
|
Pop-Location |
|
} |
|
} |
|
$ExportedMembers.Add('Set-GitFileAssumeUnchanged') |
|
|
|
|
|
function Get-GitBinaryFiles { |
|
<# |
|
.SYNOPSIS |
|
Retrieves the FileInfo object for each file git considers as a "Binary" file. |
|
|
|
.DESCRIPTION |
|
Retrieves all files that git considers as having a binary file encoding. |
|
|
|
.PARAMETER Path |
|
The path to begin the search for the binary files in. By default this will look in the current working directory. |
|
|
|
.OUTPUTS |
|
Returns the FileInfo objects for the files that git considers as having a binary file encoding. |
|
If no binary files are found then nothing is returned to the user. |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([System.IO.FileInfo])] |
|
param( |
|
[Parameter(Mandatory = $false)] |
|
[Validation.ValidatePathExists('Folder')] |
|
[string]$Path = (Get-CurrentPath) |
|
) |
|
|
|
begin { Push-Location $Path } |
|
|
|
process { |
|
try { |
|
$NonBinaryFiles = git grep -Il . |
|
$BinaryFiles = git ls-files | Where-Object { $NonBinaryFiles -notcontains $_ } |
|
foreach ($File in $BinaryFiles) { |
|
Get-Item -Path $File |
|
} |
|
} finally { |
|
Pop-Location |
|
} |
|
} |
|
} |
|
$ExportedMembers.Add('Get-GitBinaryFiles') |
|
|
|
|
|
if ($IsLinux -or $IsMacOS) { |
|
function Set-GitSettings { |
|
<# |
|
.SYNOPSIS |
|
Configures default git settings for a Linux PowerShell environment. |
|
|
|
.DESCRIPTION |
|
This function sets up the default SSH and GPG settings for git within a Linux PowerShell environment. |
|
It is not designed for use on non-Linux platforms. |
|
|
|
.PARAMETER GithubSSHKeyPath |
|
The file path to the SSH key you want to use for GitHub. |
|
If no key is provided then the function will use the default id_ed25519 key if it exists or prompts the user |
|
to select an available key. |
|
|
|
.EXAMPLE |
|
# Configures git settings using the default or user-selected SSH key. |
|
Set-GitSettings |
|
|
|
.EXAMPLE |
|
# Configures git settings using the specified SSH key at '~/.ssh/my_key'. |
|
Set-GitSettings -GithubSSHKeyPath '~/.ssh/my_key' |
|
#> |
|
[CmdletBinding()] |
|
[OutputType([Void])] |
|
param ( |
|
[Parameter(Mandatory = $false)] |
|
[IO.FileInfo]$GithubSSHKeyPath |
|
) |
|
|
|
$SSHAgent = 'ssh-agent' |
|
$SSHAdd = 'ssh-add' |
|
if ( |
|
( |
|
(Test-ApplicationExistsInPath -ApplicationName $SSHAgent) -and |
|
(Test-ApplicationExistsInPath -ApplicationName $SSHAdd) |
|
) -ne $true |
|
) { |
|
throw 'Missing required applications. Task requires both ssh-agent and ssh-add.' |
|
} |
|
try { & $SSHAgent -k *>&1 | Out-Null } catch { } |
|
$Output = & $SSHAgent -s |
|
|
|
$Env:SSH_AUTH_SOCK = $Output[0].Split('=')[1].split(';')[0] |
|
$Env:SSH_AGENT_PID = $Output[1].Split('=')[1].split(';')[0] |
|
$Env:GPG_TTY = tty |
|
|
|
# Find the right SSH key to use for github. |
|
$SSH = if (Test-Path $GithubSSHKeyPath -PathType Leaf -ErrorAction Ignore) { |
|
# If the user profides a path, and it exists, use that. |
|
$GithubSSHKeyPath |
|
} elseif (Test-Path '~/.ssh/id_ed25519' -PathType Leaf -ErrorAction Ignore) { |
|
# If no user provided key exists then try github default recommended. |
|
(Get-Item '~/.ssh/id_ed25519').FullName |
|
} else { |
|
# last resort - ask the user. |
|
$Keys = Get-ChildItem -Path '~/.ssh/' -File | Where-Object { |
|
($_.Extension -ine '.pub') -and |
|
($_.name -ine 'known_hosts') |
|
} |
|
if ($Keys) { |
|
$Index = 0 |
|
$KeyList = Foreach ($Key in $Keys) { |
|
[PSCustomObject]@{ |
|
Number = $Index |
|
Path = $Key.FullName |
|
} |
|
++$Index |
|
} |
|
$PromptAnswer = Read-Host -Prompt ( |
|
"Enter the number for the ssh key you want to use.`n" + |
|
($KeyList | Format-Table | Out-String) |
|
) |
|
$Keys[$PromptAnswer].FullName |
|
} else { |
|
Write-Error 'No SSH Keys found.' |
|
} |
|
} |
|
& $SSHAdd $SSH |
|
} |
|
$ExportedMembers.Add('Set-GitSettings') |
|
} |
|
|
|
|
|
Export-ModuleMember -Function $ExportedMembers.ToArray() |