GitAutomation.psm1
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. $registeredSsh = $false $gitCmd = Get-Command -Name 'git.exe' -ErrorAction Ignore if( $gitCmd ) { $sshExePath = Split-Path -Path $gitCmd.Path -Parent $sshExePath = Join-Path -Path $sshExePath -ChildPath '..\usr\bin\ssh.exe' -Resolve -ErrorAction Ignore if( $sshExePath ) { [Git.Automation.SshExeTransport]::Unregister() [Git.Automation.SshExeTransport]::Register($sshExePath) $registeredSsh = $true } } if( -not $registeredSsh ) { Write-Warning -Message 'SSH support is disabled. To enable SSH, please install Git for Windows. GitAutomation uses the version of SSH that ships with Git for Windows.' } $moduleRoot = $PSScriptRoot $moduleBinRoot = Join-Path -Path $moduleRoot -ChildPath 'bin' $oldLibGit2Sharp = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.FullName -like 'LibGit2Sharp*' } | ForEach-Object { $_.GetName() } | Where-Object { $_.Name -eq 'LibGit2Sharp' -and $_.Version -lt [version]'0.26.0' } if( $oldLibGit2Sharp ) { Write-Error -Message ('Unable to load GitAutomation: an older version has already been loaded. Please restart your PowerShell session.') -ErrorAction Stop } Join-Path -Path $PSScriptRoot -ChildPath 'Functions' | Where-Object { Test-Path -Path $_ -PathType Container } | Get-ChildItem -Filter '*.ps1' | ForEach-Object { . $_.FullName } function Add-GitItem { <# .SYNOPSIS Promotes changes to the Git staging area so they can be saved during the next commit. .DESCRIPTION The `Add-GitItem` function promotes new/untracked and modified files to the Git staging area. When committing changes, by default Git only commits changes that have been staged. Use this function on each file you want to commit before committing. No other files will be committed. Use the `PassThru` switch to get `IO.FileInfo` and `IO.DirectoryInfo` objects back for each file and directory added, respectively. If the items are already added or were unmodified and not added to the staging area, you'll still get objects back for them. This function implements the `git add` command. .LINK Save-GitCommit .EXAMPLE Add-GitItem -Path 'C:\Projects\GitAutomation' Demonstrates how to add all the items under a directory to the next commit to the repository in the current directory. .EXAMPLE Add-GitItem -Path 'C:\Projects\GitAutomation\Functions\Add-GitItem.ps1','C:\Projects\GitAutomation\Tests\Add-GitItem.Tests.ps1' Demonstrates how to add multiple items and files to the next commit. .EXAMPLE Get-ChildItem '.\GitAutomation\Functions','.\Tests' | Add-GitItem Demonstrates that you can pipe paths or file system objects to Add-GitItem. When passing directories, all untracked/new or modified files under that directory are added. When passing files, only that file is added. .EXAMPLE Add-GitItem -Path 'C:\Projects\GitAutomation' -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to operate on a repository that isn't the current directory. .EXAMPLE Get-ChildItem | Add-GitItem Demonstrates that you can pipe `IO.FileInfo` and `IO.DirectoryInfo` objects to `Add-GitItem`. Plain strings are also allowed. .EXAMPLE Add-GitItem -Path 'file1','directory1' -PassThru Demonstrates how to get `IO.FileInfo` and `IO.DirectoryInfo` objects returned for each file and directory, respectively. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [Alias('FullName')] [string[]] # The paths to the files/directories to add to the next commit. $Path, [string] # The path to the repository where the files should be added. The default is the current directory as returned by Get-Location. $RepoRoot = (Get-Location).ProviderPath, [Switch] # Return `IO.FileInfo` and/or `IO.DirectoryInfo` objects for each file and/or directory added, respectively. $PassThru ) begin { Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify } process { if( -not ((Test-Path -Path 'variable:repo') -and $repo) ) { return } foreach( $pathItem in $Path ) { if( -not [IO.Path]::IsPathRooted($pathItem) ) { $pathItem = Join-Path -Path $repo.Info.WorkingDirectory -ChildPath $pathItem -Resolve if( -not $pathItem ) { continue } } if( -not $pathItem.StartsWith($repo.Info.WorkingDirectory, [stringcomparison]::InvariantCultureIgnoreCase) ) { Write-Error -Message ('Item ''{0}'' can''t be added because it is not in the repository ''{1}''.' -f $pathItem,$repo.Info.WorkingDirectory) continue } if( -not (Test-Path -Path $pathItem) ) { Write-Error -Message ('Cannot find path ''{0}'' because it does not exist.' -f $pathItem) continue } [LibGit2Sharp.Commands]::Stage($repo, $pathItem, $null) if( $PassThru ) { Get-Item -Path $pathItem } } } end { if( ((Test-Path -Path 'variable:repo') -and $repo) ) { $repo.Dispose() } } } function Compare-GitTree { <# .SYNOPSIS Gets a diff of the file tree changes in a repository between two commits. .DESCRIPTION The `Compare-GitTree` function returns a `LibGit2Sharp.TreeChanges` object representing the file tree changes in a repository between two commits. The tree changes are the names of the files that have been added, removed, modified, and renamed in a git repository. Pass the name of commits to diff, such as commit hash, branch name, or tag name, to the `ReferenceCommit` and `DifferenceCommit` parameters. You must specify a commit reference name for the `ReferenceCommit` parameter. The `DifferenceCommit` parameter is optional and defaults to `HEAD`. This function implements the `git diff --name-only` command. .EXAMPLE Compare-GitTree -ReferenceCommit 'HEAD^' Demonstrates how to get the diff between the default `HEAD` commit and its parent commit referenced by `HEAD^`. .EXAMPLE Compare-GitTree -RepoRoot 'C:\build\repo' -ReferenceCommit 'tags/1.0' -DifferenceCommit 'tags/2.0' Demonstrates how to get the diff between the commit tagged with `2.0` and the older commit tagged with `1.0` in the repository located at `C:\build\repo`. #> [CmdletBinding(DefaultParameterSetName='RepositoryRoot')] [OutputType([LibGit2Sharp.TreeChanges])] param( [Parameter(ParameterSetName='RepositoryRoot')] [Alias('RepoRoot')] [string] # The root path to the repository. Defaults to the current directory. $RepositoryRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true, ParameterSetName='RepositoryObject')] [LibGit2Sharp.Repository] $RepositoryObject, [Parameter(Mandatory=$true)] [string] # A commit to compare `DifferenceCommit` against, e.g. commit hash, branch name, tag name. $ReferenceCommit, [string] # A commit to compare `ReferenceCommit` against, e.g. commit hash, branch name, tag name. Defaults to `HEAD`. $DifferenceCommit = 'HEAD' ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($RepositoryObject) { $repo = $RepositoryObject } else { $repo = Find-GitRepository -Path $RepositoryRoot -Verify if (-not $repo) { Write-Error -Message ('Unable to get diff between ''{0}'' and ''{1}''. See previous errors for more details.' -f $ReferenceCommit, $DifferenceCommit) return } } try { $oldCommit = $repo.Lookup($ReferenceCommit) $newCommit = $repo.Lookup($DifferenceCommit) if (-not $oldCommit) { Write-Error -Message ('Commit ''{0}'' not found in repository ''{1}''.' -f $ReferenceCommit, $repo.Info.WorkingDirectory) return } elseif (-not $newCommit) { Write-Error -Message ('Commit ''{0}'' not found in repository ''{1}''.' -f $DifferenceCommit, $repo.Info.WorkingDirectory) return } return , [Git.Automation.Diff]::GetTreeChanges($repo, $oldCommit, $newCommit) } finally { if (-not $RepositoryObject) { Invoke-Command -NoNewScope -ScriptBlock { $repo.Dispose() } } } } function ConvertTo-GitFullPath { [CmdletBinding()] param( [Parameter(Mandatory=$true,ParameterSetName='Path')] [string] # A path to convert to a full path. $Path, [Parameter(Mandatory=$true,ParameterSetName='Uri')] [uri] # A URI to convert to a full path. It can be a local path. $Uri ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $PSCmdlet.ParameterSetName -eq 'Uri' ) { if( $Uri.Scheme ) { return $Uri.AbsoluteUri } $Path = $Uri.ToString() } if( [IO.Path]::IsPathRooted($Path) ) { return $Path } $Path = Join-Path -Path (Get-Location) -ChildPath $Path [IO.Path]::GetFullPath($Path) } function Copy-GitRepository { <# .SYNOPSIS Clones a Git repository. .DESCRIPTION The `Copy-GitRepository` function clones a Git repository from the URL specified by `Uri` to the path specified by `DestinationPath` and checks out the `master` branch. If the repository requires authentication, pass the username/password via the `Credential` parameter. To clone a local repository, pass a file system path to the `Uri` parameter. .EXAMPLE Copy-GitRepository -Uri 'https://github.com/webmd-health-services/GitAutomation' -DestinationPath GitAutomation #> param( [Parameter(Mandatory=$true)] [string] # The URI or path to the source repository to clone. $Source, [Parameter(Mandatory=$true)] [string] # The directory where the repository should be cloned to. Must not exist or be empty. $DestinationPath, [pscredential] # The credentials to use to connect to the source repository. $Credential, [Switch] # Returns a `System.IO.DirectoryInfo` object for the new copy's `.git` directory. $PassThru ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $Source = ConvertTo-GitFullPath -Uri $Source $DestinationPath = ConvertTo-GitFullPath -Path $DestinationPath $options = New-Object 'libgit2sharp.CloneOptions' if( $Credential ) { $gitCredential = New-Object 'LibGit2Sharp.SecureUsernamePasswordCredentials' $gitCredential.Username = $Credential.UserName $gitCredential.Password = $Credential.Password $options.CredentialsProvider = { return $gitCredential } } $cancelClone = $false $options.OnProgress = { param( $Output ) Write-Verbose -Message $Output return -not $cancelClone } $lastUpdated = Get-Date $options.OnTransferProgress = { param( [LibGit2Sharp.TransferProgress] $TransferProgress ) # Only update progress every 1/10th of a second, otherwise updating the progress takes longer than the clone if( ((Get-Date) - $lastUpdated).TotalMilliseconds -lt 100 ) { return $true } $numBytes = $TransferProgress.ReceivedBytes if( $numBytes -lt 1kb ) { $unit = 'B' } elseif( $numBytes -lt 1mb ) { $unit = 'KB' $numBytes = $numBytes / 1kb } elseif( $numBytes -lt 1gb ) { $unit = 'MB' $numBytes = $numBytes / 1mb } elseif( $numBytes -lt 1tb ) { $unit = 'GB' $numBytes = $numBytes / 1gb } elseif( $numBytes -lt 1pb ) { $unit = 'TB' $numBytes = $numBytes / 1tb } else { $unit = 'PB' $numBytes = $numBytes / 1pb } Write-Progress -Activity ('Cloning {0} -> {1}' -f $Source,$DestinationPath) ` -Status ('{0}/{1} objects, {2:n0} {3}' -f $TransferProgress.ReceivedObjects,$TransferProgress.TotalObjects, $numBytes,$unit) ` -PercentComplete (($TransferProgress.ReceivedObjects / $TransferProgress.TotalObjects) * 100) Set-Variable -Name 'lastUpdated' -Value (Get-Date) -Scope 1 return (-not $cancelClone) } try { $cloneCompleted = $false $gitPath = [LibGit2Sharp.Repository]::Clone($Source, $DestinationPath, $options) if( $PassThru -and $gitPath ) { Get-Item -Path $gitPath -Force } $cloneCompleted = $true } finally { if( -not $cloneCompleted ) { $cancelClone = $true } } } function Find-GitRepository { <# .SYNOPSIS Searches a directory and its parents for a Git repository. .DESCRIPTION The `Find-GitRepository` function searches a directory for a Git repository and returns a `LibGit2Sharp.Repository` object representing that repository. If a repository isn't found, it looks up the directory tree until it finds one (i.e. it looks at the directories parent directory, then that directory's parent, then that directory's parent, etc. until it finds a repository or gets to the root directory. If it doesn't find one, nothing is returned. With no parameters, looks in the current directory and up its directory tree. If given a path with the `Path` parameter, it looks in that directory then up its directory tree. The repository object that is returned contains resources that don't get automatically removed from memory by .NET. To avoid memory leaks, you must call its `Dispose()` method when you're done using it. .OUTPUTS LibGit2Sharp.Repository. .EXAMPLE Find-GitRepository Demonstrates how to find the Git repository of the current directory. .EXAMPLE Find-GitRepository -Path 'C:\Projects\GitAutomation\GitAutomation\bin' Demonstrates how to find the Git repository that a specific directory is a part of. In this case, a `LibGit2Sharp.Repository` object is returned for the repository at `C:\Projects\GitAutomation`. #> [CmdletBinding()] [OutputType([LibGit2Sharp.Repository])] param( [string] # The path to start searching. $Path = (Get-Location).ProviderPath, [Switch] # Write an error if a repository isn't found. Usually, no error is written and nothing is returned when a repository isn't found. $Verify ) Set-StrictMode -Version 'Latest' if( -not $Path ) { $Path = (Get-Location).ProviderPath } $Path = Resolve-Path -Path $Path -ErrorAction Ignore | Select-Object -ExpandProperty 'ProviderPath' if( -not $Path ) { Write-Error -Message ('Can''t find a repository in ''{0}'' because it does not exist.' -f $PSBoundParameters['Path']) -ErrorAction $ErrorActionPreference return } $startedAt = $Path while( $Path -and -not [LibGit2Sharp.Repository]::IsValid($Path) ) { $Path = Split-Path -Parent -Path $Path } if( -not $Path ) { if( $Verify ) { Write-Error -Message ('Path ''{0}'' not in a Git repository.' -f $startedAt) -ErrorAction $ErrorActionPreference } return } return Get-GitRepository -RepoRoot $Path } function Get-GitBranch { <# .SYNOPSIS Gets the branches in a Git repository. .DESCRIPTION The `Get-GitBranch` function returns a list of all the branches in a repository. Use the `Current` switch to return just the current branch. It defaults to the current repository. Use the `RepoRoot` parameter to specify an explicit path to another repo. .EXAMPLE Get-GitBranch -RepoRoot 'C:\Projects\GitAutomation' -Current Returns an object representing the current branch for the specified repo. .EXAMPLE Get-GitBranch Returns objects for all the branches in the current directory. #> [CmdletBinding()] [OutputType([Git.Automation.BranchInfo])] param( [string] # Specifies which git repository to check. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [Switch] # Get the current branch only. Otherwise all branches are returned. $Current ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { if( $Current ) { New-Object Git.Automation.BranchInfo $repo.Head return } else { $repo.Branches | ForEach-Object { New-Object Git.Automation.BranchInfo $_ } return } } finally { $repo.Dispose() } } function Get-GitCommit { <# .SYNOPSIS Gets the sha-1 ID for specific changes in a Git repository. .DESCRIPTION The `Get-GitCommit` gets all the commits in a repository, from most recent to oldest. To get a commit for a specific named revision, e.g. HEAD, a branch, a tag), pass the name to the `Revision` parameter. To get the commit of the current checkout, pass `HEAD` to the `Revision` parameter. #> [CmdletBinding(DefaultParameterSetName='All')] [OutputType([Git.Automation.CommitInfo])] param( [Parameter(ParameterSetName='All')] [switch] # Get all the commits in the repository. $All, [Parameter(Mandatory=$true,ParameterSetName='Lookup')] [string] # A named revision to get, e.g. `HEAD`, a branch name, tag name, etc. # To get the commit of the current checkout, pass `HEAD`. $Revision, [Parameter(ParameterSetName='CommitFilter')] [string] # The starting commit from which to generate a list of commits. Defaults to `HEAD`. $Since = 'HEAD', [Parameter(Mandatory=$true,ParameterSetName='CommitFilter')] [string] # The commit and its ancestors which will be excluded from the returned commit list which starts at `Since`. $Until, [Parameter(ParameterSetName='CommitFilter')] [switch] # Do not include any merge commits in the generated commit list. $NoMerges, [string] # The path to the repository. Defaults to the current directory. $RepoRoot ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot if( -not $repo ) { return } try { if( $PSCmdlet.ParameterSetName -eq 'All' ) { $filter = New-Object -TypeName 'LibGit2Sharp.CommitFilter' $filter.IncludeReachableFrom = $repo.Refs $repo.Commits.QueryBy($filter) | ForEach-Object { New-Object -TypeName 'Git.Automation.CommitInfo' -ArgumentList $_ } return } elseif( $PSCmdlet.ParameterSetName -eq 'Lookup' ) { $change = $repo.Lookup($Revision) if( $change ) { return New-Object -TypeName 'Git.Automation.CommitInfo' -ArgumentList $change } else { Write-Error -Message ('Commit ''{0}'' not found in repository ''{1}''.' -f $Revision,$repo.Info.WorkingDirectory) -ErrorAction $ErrorActionPreference return } } elseif( $PSCmdlet.ParameterSetName -eq 'CommitFilter') { $IncludeFromCommit = $repo.Lookup($Since) $ExcludeFromCommit = $repo.Lookup($Until) if (-not $IncludeFromCommit) { Write-Error -Message ('Commit ''{0}'' not found in repository ''{1}''.' -f $Since,$repo.Info.WorkingDirectory) -ErrorAction $ErrorActionPreference return } elseif (-not $ExcludeFromCommit) { Write-Error -Message ('Commit ''{0}'' not found in repository ''{1}''.' -f $Until,$repo.Info.WorkingDirectory) -ErrorAction $ErrorActionPreference return } elseif ($IncludeFromCommit.Sha -eq $ExcludeFromCommit.Sha) { Write-Error -Message ('Commit reference ''{0}'' and ''{1}'' refer to the same commit with hash ''{2}''.' -f $Since,$Until,$IncludeFromCommit.Sha) return } $CommitFilter = New-Object -TypeName LibGit2Sharp.CommitFilter $CommitFilter.IncludeReachableFrom = $IncludeFromCommit.Sha $CommitFilter.ExcludeReachableFrom = $ExcludeFromCommit.Sha $filteredCommits = $repo.Commits.QueryBy($CommitFilter) if ($NoMerges) { $filteredCommits = $filteredCommits | Where-Object { $_.Parents.Count -le 1 } } $filteredCommits | ForEach-Object { New-Object -TypeName 'Git.Automation.CommitInfo' -ArgumentList $_ } } } finally { $repo.Dispose() } } function Get-GitConfiguration { <# .SYNOPSIS Gets Git configuration. .DESCRIPTION The `Get-GitConfiguration` function returns Git's configuration as `LibGit2Sharp.ConfigurationEntry` objects. When run outside a repository, it gets configuration from the user's and system's configuration files. When run from inside a repository, it also returns that repository's settings. The objects returned have the following properties: * `Key`: the key/name of the setting. This will be the section and name seperated by a dot, e.g. `user.name`. * `Value`: the setting's value. * `Level`: a `LibGit2Sharp.ConfigurationLevel` enumeration value indicating at what level/scope the setting is defined. `Local` means its set in a repository's `.git\config` file. `Global` means its set in the user's `.gitconfig` file. `Xdg` means it set in the user's `.config\git\config` file. `System` means its set in the global `gitconfig` file. `ProgramData` means it is defined in Git's system-wide `Git\config` file in Windows' `ProgramData` directory. You can explicitly set the repository whose settings to get with the `RepoRoot` parameter. Settings at higher levels will also be returned. You can read configuration from a specific file by passing the file's path to the `Path` parameter. When reading from a specific file, settings at higher levels (e.g. global and system) are also returned. To return a specific configuration setting, pass its name to the `Name` parameter. If a setting with that name doesn't exist, nothing is returned. Sections and setting names should be seperated by periods, e.g. `user.name`. .EXAMPLE Get-GitConfiguration Demonstrates how to get all Git configuration. .EXAMPLE Get-GitConfiguration -Path 'template.gitconfig' Demonstrates how to read configuration from a specific git config file. Settings at higher levels (global/user and system) are still returned. .EXAMPLE Get-GitConfiguration -Path 'template.gitconfig' | Where-Object { $_.Level -eq [LibGit2Sharp.ConfigurationLevel]::Local } Demonstrates how to read configuration from a specific git config file and filter out all settings that didn't come from that file. .EXAMPLE Get-GitConfiguration -Name 'user.email' Demonstrates how to get a specific setting. #> [CmdletBinding(DefaultParameterSetName='ByScope')] [OutputType([LibGit2Sharp.ConfigurationEntry[string]])] param( [Parameter(Position=0)] [string] # The name of the configuration variable to get. By default all configuration settings are returned. The name should be the section and name seperated by a dot, e.g. `user.name`. $Name, [Parameter(Mandatory=$true,ParameterSetName='ByPath')] [string] # The path to a specific file from which to read configuration. If this file doesn't exist, it is created. $Path, [Parameter(ParameterSetName='ByScope')] [string] # The path to the repository whose configuration variables to get. Defaults to the repository the current directory is in. $RepoRoot ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $PSCmdlet.ParameterSetName -eq 'ByPath' ) { if( -not (Test-Path -Path $Path -PathType Leaf) ) { New-Item -Path $Path -ItemType 'File' -Force | Write-Verbose } $Path = Resolve-Path -Path $Path | Select-Object -ExpandProperty 'ProviderPath' $config = [LibGit2Sharp.Configuration]::BuildFrom($Path) try { if( -not $Name ) { return $config } [Git.Automation.ConfigurationExtensions]::GetString( $config, $Name, 'Local' ) } finally { $config.Dispose() } return } $pathParam = @{} if( $RepoRoot ) { $pathParam['Path'] = $RepoRoot } $value = $null $repo = Find-GitRepository @pathParam -Verify -ErrorAction Ignore if( $repo ) { try { if( -not $Name ) { return $repo.Config } $value = [Git.Automation.ConfigurationExtensions]::GetString( $repo.Config, $Name ) } finally { $repo.Dispose() } } if( -not $value ) { $config = [LibGit2Sharp.Configuration]::BuildFrom([nullstring]::Value,[nullstring]::Value) try { if( -not $Name ) { return $config } $value = [Git.Automation.ConfigurationExtensions]::GetString( $config, $Name ) } finally { $config.Dispose() } } return $value } function Get-GitRepository { <# .SYNOPSIS Gets an object representing a Git repository. .DESCRIPTION The `Get-GitRepository` function gets a `LibGit2Sharp.Repository` object representing a Git repository. By default, it gets the current directory's repository. You can get an object for a specific repository using the `RepoRoot` parameter. If the `RepoRoot` path doesn't point to the root of a Git repository, or, if not using the `RepoRoot` parameter and the current directory isn't the root of a Git repository, you'll get an error. The repository object contains resources that don't get automatically removed from memory by .NET. To avoid memory leaks, you must call its `Dispose()` method when you're done using it. .EXAMPLE Get-GitRepository Demonstrates how to get a `LibGit2Sharp.Repository` object for the repository in the current directory. .EXAMPLE Get-GitRepository -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to get a `LibGit2Sharp.Repository` object for a specific repository. #> [CmdletBinding()] [OutputType([LibGit2Sharp.Repository])] param( [string] # The root to the repository to get. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath ) Set-StrictMode -Version 'Latest' $RepoRoot = Resolve-Path -Path $RepoRoot -ErrorAction Ignore | Select-Object -ExpandProperty 'ProviderPath' if( -not $RepoRoot ) { Write-Error -Message ('Repository ''{0}'' does not exist.' -f $PSBoundParameters['RepoRoot']) return } try { New-Object 'LibGit2Sharp.Repository' ($RepoRoot) } catch { Write-Error -ErrorRecord $_ } } function Get-GitRepositoryStatus { <# .SYNOPSIS Gets information about all added, untracked, and modified files in a repository. .DESCRIPTION The `Get-GitRepositoryStatus` commands gets information about which files in your working directory are new, untracked, or modified, including files that have been staged for the next commit. It gets information about each uncommitted change in your repository. Ignored items are not returned unless you provide the `IncludeIgnored` switch. You can get status for specific files and directories with the Path parameter. If you provide a `RepoRoot` parameter to work with a specific repository, the values of the `Path` parameter should be relative to the root of that repository. With no `RepoRoot` parameter, the paths in the `Path` parameter are treated as relative to the current directory. Wildcards are supported and are passed directly to Git to evaluate (i.e. use Git wildcard syntax not PowerShell's). The `LibGit2Sharp.StatusEntry` objects returned have several extended type data members added. You should use these members instead of using the object's `State` property. * `IsStaged`: `$true` if the item has been staged for the next commit; `$false` otherwise. * `IsAdded`: returns `$true` if the item is new in the working directory or has been staged for the next commit; `$false` otherwise. * `IsConflicted`: returns `$true` if the item was merged and currently has conflicts; `$false` otherwise. * `IsDeleted`: returns `$true` if the item was deleted from the working directory or has been staged for removal in the next commit; `$false` otherwise. * `IsIgnored`: returns `$true` if the item is ignored; `$false` otherwise. You'll only see ignored items if you use the `IncludeIgnored` switch. * `IsModified`: returns `$true` if the item is modified; `$false` otherwise. * `IsRenamed`: returns `$true` if the item was renamed; `$false` otherwise. * `IsTypeChanged`: returns `$true` if the item's type was changed; `$false` otherwise. * `IsUnchanged`: returns `$true` if the item is unchanged; `$false` otherwise. * `IsUnreadable`: returns `$true` if the item is unreadable; `$false` otherwise. * `IsUntracked`: returns `$true` if the item is untracked (i.e. hasn't been staged or added to the repository); `$false` otherwise. When displayed in a table (the default), the first column will show characters that indicate the state of each item, e.g. State FilePath ----- -------- a GitAutomation\Formats\LibGit2Sharp.StatusEntry.ps1xml a GitAutomation\Functions\Get-GitRepositoryStatus.ps1 m GitAutomation\GitAutomation.psd1 a GitAutomation\Types\LibGit2Sharp.StatusEntry.types.ps1xml a Tests\Get-GitRepositoryStatus.Tests.ps1 The state will display: * `i` if the item is ignored (i.e. `IsIgnored` returns `$true`) * `a` if the item is untracked or staged for the next commit (i.e. `IsAdded` returns `$true`) * `m` if the item was modified (i.e. `IsModified` returns `$true`) * `d` if the item was deleted (i.e. `IsDeleted` returns `$true`) * `r` if the item was renamed (i.e. `IsRenamed` returns `$true`) * `t` if the item's type was changed (i.e. `IsTypeChanged` returns `$true`) * `?` if the item can't be read (i.e. `IsUnreadable` returns `$true`) * `!` if the item was merged with conflicts (i.e. `IsConflicted` return `$true`) If no state characters are shown, the file is unchanged (i.e. `IsUnchanged` return `$true`). This function implements the `git status` command. .EXAMPLE Get-GitRepositoryStatus Demonstrates how to get the status of any uncommitted changes for the repository in the current directory. .EXAMPLE Get-GitRepositoryStatus -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to get the status of any uncommitted changes for the repository at a specific location. .EXAMPLE Get-GitRepositoryStatus -Path 'build.ps1','*.cs' Demonstrates how to get the status for specific files at or under the current directory using the Path parameter. In this case, only modified files named `build.ps1` or that match the wildcard `*.cs` under the current directory will be returned. .EXAMPLE Get-GitRepositoryStatus -Path 'build.ps1','*.cs' -RepoRoot 'C:\Projects\GitAutomation` Demonstrates how to get the status for specific files under the root of a specific repository. In this case, only modified files named `build.ps1` or that match the wildcard `*.cs` under `C:\Projects\GitAutomation` will be returned. #> [CmdletBinding()] [OutputType([LibGit2Sharp.StatusEntry])] param( [Parameter(Position=0)] [string[]] # The path to specific files and/or directories whose status to get. Git-style wildcards are supported. # # If no `RepoRoot` parameter is provided, these paths are evaluated as relative to the current directory. If a `RepoRoot` parameter is provided, these paths are evaluated as relative to the root of that repository. $Path, [Switch] # Return ignored files and directories. The default is to not return them. $IncludeIgnored, [string] # The path to the repository whose status to get. $RepoRoot = (Get-Location).ProviderPath ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { $statusOptions = New-Object -TypeName 'LibGit2Sharp.StatusOptions' if( $IncludeIgnored ) { $statusOptions.RecurseIgnoredDirs = $true } $currentLocation = (Get-Location).ProviderPath if( -not $currentLocation.EndsWith([IO.Path]::DirectorySeparatorChar) ) { $currentLocation = '{0}{1}' -f $currentLocation,[IO.Path]::DirectorySeparatorChar } if( -not $currentLocation.StartsWith($repo.Info.WorkingDirectory) ) { Push-Location -Path $repo.Info.WorkingDirectory -StackName 'Get-GitRepositoryStatus' } $repoRootRegex = $repo.Info.WorkingDirectory.TrimEnd([IO.Path]::DirectorySeparatorChar) $repoRootRegex = [regex]::Escape($repoRootRegex) $repoRootRegex = '^{0}\\?' -f $repoRootRegex try { if( $Path ) { $statusOptions.PathSpec = $Path | ForEach-Object { $pathItem = $_ if( [IO.Path]::IsPathRooted($_) ) { return $_ } $fullPath = Join-Path -Path (Get-Location).ProviderPath -ChildPath $_ try { return [IO.Path]::GetFullPath($fullPath) } catch { return $pathItem } } | ForEach-Object { $_ -replace $repoRootRegex,'' } | ForEach-Object { $_ -replace ([regex]::Escape([IO.Path]::DirectorySeparatorChar)),'/' } } $repo.RetrieveStatus($statusOptions) | Where-Object { if( $IncludeIgnored ) { return $true } return -not $_.IsIgnored } } finally { Pop-Location -StackName 'Get-GitRepositorySTatus' -ErrorAction Ignore } } finally { $repo.Dispose() } } function Get-GitTag{ <# .SYNOPSIS Gets the tags in a Git repository. .DESCRIPTION The `Get-GitTag` function returns a list of all the tags in a Git repository. To get a specific tag, pass its name to the `Name` parameter. Wildcard characters are supported in the tag name. Only tags that match the wildcard pattern will be returned. .EXAMPLE Get-GitTag Demonstrates how to get all the tags in a repository. .EXAMPLE Get-GitTag -Name 'LastSuccessfulBuild' Demonstrates how to return a specific tag. If no tag matches, then `$null` is returned. .EXAMPLE Get-GitTag -Name '13.8.*' -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to return all tags that match a wildcard in the given repository.' #> [CmdletBinding()] [OutputType([Git.Automation.TagInfo])] param( [string] # Specifies which git repository to check. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [string] # The name of the tag to return. Wildcards accepted. $Name ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { $repo.Tags | ForEach-Object { New-Object Git.Automation.TagInfo $_ } | Where-Object { if( $PSBoundParameters.ContainsKey('Name') ) { return $_.Name -like $Name } return $true } } finally { $repo.Dispose() } } function Merge-GitCommit { <# .SYNOPSIS Merges a commit into the current branch. .DESCRIPTION The `Merge-GitCommit` function merges a commit into the current branch. The commit can be identified with its ID, by a tag name, or branch name. It returns a `Git.Automation.MergeResult` object, which has two properties: * `Status`: the status of the merge. It will be one of the following values: * `Conflicts`: when there are conflicts with the merge. * `FastForward`: when the merge resulted in a fast-forward. * `NonFastForward`: when a merge commit was created. * `UpToDate`: when nothing needed to be merged. * `Commit`: the merge commit (if one was created). If there are conflicts, the conflicts are left in place. You can use your preferred merge tool to resolve the conflicts and then commit. If this script is running non-interactively, you probably don't want any conflict markers hanging out in your local files. Use the "-NonInteractive" switch to prevent conflict files from remaining. By default, the function operates on the Git repository in the current directory. Use the `RepoRoot` parameter to target a different repository. .EXAMPLE Merge-GitCommit -Revision 'develop' Demonstrates how to merge a branch into the current branch. #> [CmdletBinding()] [OutputType([Git.Automation.MergeResult])] param( [string] # The path to the repository where the files should be added. The default is the current directory as returned by Get-Location. $RepoRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true)] [string] # The revision to merge into the current commit (i.e. HEAD). A revision can be a specific commit ID/sha (short or long), branch name, tag name, etc. Run git help gitrevisions or go to https://git-scm.com/docs/gitrevisions for full documentation on Git's revision syntax. $Revision = "HEAD", [string] [ValidateSet('No','Only')] # Controls whether or not to do a fast-forward merge. By default, "Merge-GitCommit" will try to do a fast-forward merge if it can. If it can't it will create a new merge commit. Options are: # # * `No`: Don't do a fast-forward merge. Always create a merge commit. # * `Only`: Only do a fast-forward merge. No new merge commit is created. $FastForward, [Switch] # The merge is happening non-interactively. If there are any conflicts, the working directory will be left in the state it was in before the merge, i.e. there will be no conflict markers left in any files. $NonInteractive ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $repo = Get-GitRepository -RepoRoot $RepoRoot if( -not $repo ) { return } [LibGit2Sharp.MergeOptions]$mergeOptions = New-Object -TypeName 'LibGit2Sharp.MergeOptions' $mergeOptions.CommitOnSuccess = $true $mergeOptions.FailOnConflict = $false if( $NonInteractive ) { $mergeOptions.FailOnConflict = $true } $mergeOptions.FindRenames = $true if( $FastForward -eq 'No' ) { $mergeOptions.FastForwardStrategy = [LibGit2Sharp.FastForwardStrategy]::NoFastForward } elseif( $FastForward -eq 'Only' ) { $mergeOptions.FastForwardStrategy = [LibGit2Sharp.FastForwardStrategy]::FastForwardOnly } $signature = $repo.Config.BuildSignature((Get-Date)) try { $result = $repo.Merge($Revision,$signature,$mergeOptions) New-Object -TypeName 'Git.Automation.MergeResult' -ArgumentList $result } catch { Write-Error -Exception $_.Exception } finally { $repo.Dispose() } } function New-GitBranch { <# .SYNOPSIS Creates a new branch in a Git repository. .DESCRIPTION The `New-GitBranch` creates a new branch in a Git repository and then switches to (i.e. checks out) that branch. It defaults to the current repository. Use the `RepoRoot` parameter to specify an explicit path to another repo. This function implements the `git branch <branchname> <startpoint>` and `git checkout <branchname>` commands. .EXAMPLE New-GitBranch -RepoRoot 'C:\Projects\GitAutomation' -Name 'develop' Demonstrates how to create a new branch named 'develop' in the specified repository. .EXAMPLE New-GitBranch -Name 'develop Demonstrates how to create a new branch named 'develop' in the current directory. #> [CmdletBinding()] param( [string] # Specifies which git repository to add a branch to. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true)] [string] # The name of the new branch. $Name, [string] # The revision where the branch should be started/created. A revision can be a specific commit ID/sha (short or long), branch name, tag name, etc. Run git help gitrevisions or go to https://git-scm.com/docs/gitrevisions for full documentation on Git's revision syntax. $Revision = "HEAD" ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { if( Test-GitBranch -RepoRoot $RepoRoot -Name $Name ) { Write-Warning ('Branch {0} already exists in repository {1}' -f $Name, $RepoRoot) return } $newBranch = $repo.Branches.Add($Name, $Revision) $checkoutOptions = New-Object LibGit2Sharp.CheckoutOptions [LibGit2Sharp.Commands]::Checkout($repo, $newBranch, $checkoutOptions) } catch [LibGit2Sharp.LibGit2SharpException] { Write-Error ("Could not create branch '{0}' from invalid starting point: '{1}'" -f $Name, $Revision) } finally { $repo.Dispose() } } function New-GitRepository { <# .SYNOPSIS Creates a new Git repository. .DESCRIPTION The `New-GitRepository` function creates a new Git repository. Set the `Path` parameter to the directory where the repository should be created. If the path does not exist, it is created and that directory becomes the new repository's root. If the path does exist, it also becomes the root of a new repository. If the path exists and it is already a repository, nothing happens. To create a bare repository (i.e. a repository that doesn't have a working directory) use the `Bare` switch. This function implements the `git init` command. .OUTPUTS Git.Automation.RepositoryInfo. .EXAMPLE New-GitRepository -Path 'C:\Projects\MyCoolNewRepo' Demonstrates how to create a new Git repository. In this case, a new repository is created in `C:\Projects\MyCoolNewRepo'. .EXAMPLE New-GitRepository -Path 'C:\Projects\MyCoolNewRepo' -Bare Demonstrates how to create a repository that doesn't have a working directory. Git calls these "Bare" repositories. #> [CmdletBinding(SupportsShouldProcess=$true)] [OutputType([Git.Automation.RepositoryInfo])] param( [Parameter(Mandatory=$true)] [string] # The path to the repository to create. $Path, [Switch] $Bare ) Set-StrictMode -Version 'Latest' if( -not [IO.Path]::IsPathRooted($Path) ) { $Path = Join-Path -Path (Get-Location).ProviderPath -ChildPath $Path $Path = [IO.Path]::GetFullPath($Path) } $whatIfMessage = 'create Git repository at ''{0}''' -f $Path if( -not $PSCmdlet.ShouldProcess($whatIfMessage, $whatIfMessage, 'New-GitRepository' ) ) { return } $repoPath = [LibGit2Sharp.Repository]::Init($Path, $Bare.IsPresent) $repo = New-Object 'LibGit2Sharp.Repository' $repoPath try { return New-Object 'Git.Automation.RepositoryInfo' $repo.Info } finally { $repo.Dispose() } } function New-GitSignature { <# .SYNOPSIS Creates an author signature object used to identify who created a commit. .DESCRIPTION The `New-GitSignature` object creates `LibGit2Sharp.Signature` objects. These objects are added when committing changes to identify the author of the commit and when the commit was made. With no parameters, this function reads author metadata from the "user.name" and "user.email" user level or system level configuration. If there is no user or system-level "user.name" or "user.email" setting, you'll get an error and nothing will be returned. To use explicit author information, pass the author's name and email address to the "Name" and "EmailAddress" parameters. .EXAMPLE New-GitSignature Demonstrates how to get create a Git author signature from the current user's user-level and system-level Git configuration files. .EXAMPLE New-GitSignature -Name 'Jock Nealy' -EmailAddress 'email@example.com' Demonstrates how to create a Git author signature using an explicit name and email address. #> [CmdletBinding(DefaultParameterSetName='FromConfiguration')] [OutputType([LibGit2Sharp.Signature])] param( [Parameter(Mandatory=$true,ParameterSetName='FromParameter')] [string] # The author's name, i.e. GivenName Surname. $Name, [Parameter(Mandatory=$true,ParameterSetName='FromParameter')] [string] # The author's email address. $EmailAddress, [Parameter(Mandatory=$true,ParameterSetName='FromRepositoryConfiguration')] [string] $RepoRoot ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState function Get-Signature { param( [LibGit2Sharp.Configuration] $Configuration ) $signature = $Configuration.BuildSignature([DateTimeOffset]::Now) if( -not $signature ) { Write-Error -Message ('Failed to build author signature from Git configuration files. Please pass custom author information to the "Name" and "EmailAddress" parameters or set author information in Git''s user-level configuration files by running these commands: git config --global user.name "GIVEN_NAME SURNAME" git config --global user.email "email@example.com" ') -ErrorAction $ErrorActionPreference return } return $signature } if( $PSCmdlet.ParameterSetName -eq 'FromRepositoryConfiguration' ) { $repo = Get-GitRepository -RepoRoot $RepoRoot if( -not $repo ) { return } try { return Get-Signature -Configuration $repo.Config } finally { $repo.Dispose() } } if( $PSCmdlet.ParameterSetName -eq 'FromConfiguration' ) { $blankGitConfigPath = Join-Path -Path $moduleBinRoot -ChildPath 'gitconfig' -Resolve [LibGit2Sharp.Configuration]$config = [LibGit2Sharp.Configuration]::BuildFrom($blankGitConfigPath) try { return Get-Signature -Configuration $config } finally { $config.Dispose() } } New-Object -TypeName 'LibGit2Sharp.Signature' -ArgumentList $Name,$EmailAddress,([DateTimeOffset]::Now) } function New-GitTag{ <# .SYNOPSIS Creates a new tag in a Git repository. .DESCRIPTION The `New-GitTag` function creates a tag in a Git repository. A tag is a name that references/points to a specific commit in the repository. By default, the tag points to the commit checked out in the working directory. To point to a specific commit, pass the commit ID to the `Target` parameter. If the tag already exists, this function will fail. If you want to update an existing tag to point to a different commit, use the `Force` switch. This function implements the `git tag <tagname> <target>` command. .EXAMPLE New-GitTag -Name 'BranchBaseline' Creates a new tag, `BranchBaseline`, for the HEAD of the current directory. .EXAMPLE New-GitTag -Name 'BranchBaseline' -Target 'branch' Demonstrates how to create a tag pointing to the head of a branch. .EXAMPLE New-GitTag -Name 'BranchBaseline' -Force Demonstrates how to change the target a tag points to, to the current HEAD. #> [CmdletBinding()] param( [string] # Specifies which git repository to add the tag to. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true)] [string] # The name of the tag. $Name, [string] # The revision the tag should point to/reference. A revision can be a specific commit ID/sha (short or long), branch name, tag name, etc. Run git help gitrevisions or go to https://git-scm.com/docs/gitrevisions for full documentation on Git's revision syntax. $Revision = "HEAD", [Switch] # Overwrite existing tag to point at new target $Force ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { if( -not $Force -and (Test-GitTag -RepoRoot $RepoRoot -Name $Name) ) { Write-Error ("Tag '{0}' already exists. Please use a different tag name." -f $Name) return } $validTarget = $repo.Lookup($Revision) if( -not $validTarget ) { Write-Error ("No valid git object identified by '{0}' exists in the repository." -f $Revision) return } $allowOverwrite = $false if( $Force ) { $allowOverwrite = $true } $repo.Tags.Add($Name, $Revision, $allowOverwrite) } finally { $repo.Dispose() } } function Receive-GitCommit { <# .SYNOPSIS Downloads all branches (and their commits) from remote repositories. .DESCRIPTION The `Recieve-GitCommit` function gets all the commits on all branches from all remote repositories and brings them into your repository. It defaults to the repository in the current directory. Pass the path to a different repository to the `RepoRoot` parameter. This function implements the `git fetch` command. .EXAMPLE Receive-GitCommit Demonstrates how to get all branches from a remote repository. #> [CmdletBinding()] param( [string] # The repository to fetch updates for. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [pscredential] # The credentials to use to connect to the source repository. $Credential ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } $options = New-Object 'libgit2sharp.FetchOptions' if( $Credential ) { $gitCredential = New-Object 'LibGit2Sharp.SecureUsernamePasswordCredentials' $gitCredential.Username = $Credential.UserName $gitCredential.Password = $Credential.Password $options.CredentialsProvider = { return $gitCredential } } try { foreach( $remote in $repo.Network.Remotes ) { [string[]]$refspecs = $remote.FetchRefSpecs | Select-Object -ExpandProperty 'Specification' [LibGit2Sharp.Commands]::Fetch($repo, $remote.Name, $refspecs, $options, $null) } } finally { $repo.Dispose() } } function Remove-GitConfiguration { <# .SYNOPSIS Removes/unsets a Git configuration value. .DESCRIPTION The `Remove-GitConfiguration` function removes/unsets a Git configuration value. Pass the name of the setting to the `Name` parameter (sections and names should be seperated by a dot, e.g. `user.name`). Pass the scope at which you want the configuration removed to the `Scope` parameter. Values are: * `Local`: the setting will be removed from the repository in the current working directory's `.git\config` file. * `Global`: the setting will be removed from the user's `.gitconfig` file. * `Xdg`: the setting will be removed from the user's `.config\git\config` file. * `System`: the setting will be removed from Git's global `gitconfig` file. * `ProgramData` the setting will be removed from the `Git\config` file in Windows' `ProgramData` directory. To work on a specific repository, pass its path to the `RepoRoot` directory. To operate on a specific file, pass its path to the `Path` directory. If the setting doesn't exist, nothing happens. .EXAMPLE Remove-GitConfiguration -Name 'user.name' -Scope Global Demonstrates how to removes a setting from a given scope. In this case, the `user.name` setting will be removed from the user's `.gitconfig` file. .EXAMPLE Remove-GitConfiguration -Name 'user.name' -Scope Local -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to remove a setting from a specific repository. In this case, the `user.name` setting is removed from the `.git\config` file in the `C:\Projects\GitAutomation` repository. .EXAMPLE Remove-GitConfiguration -Name 'user.name' -Path 'C:\Projects\GitAutomation\template.gitconfig' Demonstrates how to remove a setting from a specific file. In this case, the `user.name` setting is removed from the `C:\Projects\GitAutomation\template.gitconfig` file. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the configuration variable to remove. $Name, [Parameter(ParameterSetName='ByScope')] [LibGit2Sharp.ConfigurationLevel] # Where to remove the configuration value. Local means the value will be removed from the repository in the current working directory. Global means remove from the current user's `.gitconfig` file. Xdg means remove from the user's `.config\git\config` file. System means remove from Git's system-wide configuration file. `ProgramData` means remove from the Git's config file in the `Git` directory in Windows' ProgramData directory. The default is `Local`. $Scope = ([LibGit2Sharp.ConfigurationLevel]::Local), [Parameter(Mandatory=$true,ParameterSetName='ByPath')] [string] # The path to a specific file where a configuration value should be removed. $Path, [Parameter(ParameterSetName='ByScope')] [string] # The path to the repository whose configuration variables to remove. Defaults to the repository the current directory is in. $RepoRoot = (Get-Location).Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if( $PSCmdlet.ParameterSetName -eq 'ByPath' ) { if( -not (Test-Path -Path $Path -PathType Leaf) ) { return } $Path = Resolve-Path -Path $Path | Select-Object -ExpandProperty 'ProviderPath' $config = [LibGit2Sharp.Configuration]::BuildFrom($Path) try { $config.Unset( $Name, [LibGit2Sharp.ConfigurationLevel]::Local ) } finally { $config.Dispose() } return } $pathParam = @{} if( $RepoRoot ) { $pathParam['Path'] = $RepoRoot } Write-Verbose -Message ('Removing configuration "{0}".' -f $Name) if( $Scope -eq [LibGit2Sharp.ConfigurationLevel]::Local ) { $repo = Find-GitRepository @pathParam -Verify -ErrorAction Ignore if( -not $repo ) { Write-Error -Message ('There is no Git repository at "{0}". Unable to unset "{1}".' -f $RepoRoot,$Name) return } try { $repo.Config.Unset($Name,$Scope) } finally { $repo.Dispose() } return } $config = [LibGit2Sharp.Configuration]::BuildFrom([nullstring]::Value,[nullstring]::Value) try { $config.Unset($Name,$Scope) } finally { $config.Dispose() } } function Remove-GitItem { <# .SYNOPSIS Function to Remove files from both working directory and in the repository .DESCRIPTION This function will delete the files from the working directory and stage the files to be deleted in the next commit. Multiple filepaths can be passed at once. .EXAMPLE Remove-GitItem -RepoRoot $repoRoot -Path 'file.ps1' .Example Remove-GitItem -Path 'file.ps1' .Example Get-ChildItem '.\GitAutomation\Functions','.\Tests' | Remove-GitItem #> param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [String[]] # The paths to the files/directories to remove in the next commit. $Path, [string] # The path to the repository where the files should be removed. The default is the current directory as returned by Get-Location. $RepoRoot = (Get-Location).ProviderPath ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } foreach( $pathItem in $Path ) { if( -not [IO.Path]::IsPathRooted($pathItem) ) { $pathItem = Join-Path -Path $repo.Info.WorkingDirectory -ChildPath $pathItem } [LibGit2Sharp.Commands]::Remove($repo, $pathItem, $true, $null) } $repo.Dispose() } function Save-GitCommit { <# .SYNOPSIS Commits changes to a Git repository. .DESCRIPTION The `Save-GitCommit` function commits changes to a Git repository. Those changes must be staged first with `git add` or the `GitAutomation` module's `Add-GitItem` function. If there are no changes staged, nothing happens, and nothing is returned. You are required to pass a commit message with the `Message` parameter. This module is intended to be used by non-interactive repository automation scripts, so opening in an editor is not supported. Implements the `git commit` command. .OUTPUTS Git.Automation.CommitInfo .LINK Add-GitItem .EXAMPLE Save-GitCommit -Message 'Creating Save-GitCommit function.' Demonstrates how to commit staged changes in a Git repository. In this example, the repository is assumed to be in the current directory. .EXAMPLE Save-GitCommit -Message 'Creating Save-GitCommit function.' -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to commit changes to a repository other than the current directory. .EXAMPLE Save-GitCommit -Message 'Creating Save-GitCommit function.' -Signature (New-GitSignature -Name 'Name' -EmailAddress 'email@example.com') Demonstrates how to set custom author metadata. In this case, the commit will be from user "Name" whose email address is "email@example.com". #> [CmdletBinding()] [OutputType([Git.Automation.CommitInfo])] param( [Parameter(Mandatory=$true)] [string] # The commit message. $Message, [string] # The repository where to commit staged changes. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [LibGit2Sharp.Signature] # Author metadata. If not provided, it is pulled from configuration. To create an author/signature object, # # New-GitSignature -name 'Name' -EmailAddress 'email@example.com' # $Signature ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { $commitOptions = New-Object 'LibGit2Sharp.CommitOptions' $commitOptions.AllowEmptyCommit = $false if( -not $Signature ) { $Signature = New-GitSignature -RepoRoot $RepoRoot -ErrorAction Ignore if( -not $Signature ) { Write-Error -Message ('Failed to build author signature from Git configuration files. Pass an author signature to the "Signature" parameter (use the "New-GitSignature" function to create an author signature) or set author information in Git''s user-level configuration files by running these commands: git config --global user.name "GIVEN_NAME SURNAME" git config --global user.email "email@example.com" ') return } } $repo.Commit( $Message, $Signature, $Signature, $commitOptions ) | ForEach-Object { New-Object 'Git.Automation.CommitInfo' $_ } } catch [LibGit2Sharp.EmptyCommitException] { $Global:Error.RemoveAt(0) Write-Warning -Message ('Nothing to commit. Git only commits changes that are staged. To stage changes, use the Add-GitItem function or the `git add` command.') } catch { Write-Error -ErrorRecord $_ } finally { $repo.Dispose() } } function Send-GitBranch { <# .SYNOPSIS Pushes the current branch to a remote repository, merging in changes from the remote branch, if necessary. .DESCRIPTION The `Send-GitBranch` function sends the changes in the current branch to a remote repository. If there are any new changes for that branch on the remote repository, they are pulled in and merged with the local branch using the `Sync-GitBranch` function. Use the `MergeStrategy` argument to control how new changes are merged into your branch. The default is to use the `merge.ff` Git setting, which is to fast-forward when possible, merge otherwise. The `Retry` parameter controls how many pull/merge/push attempts to make. The default is "5". Returns a `Git.Automation.SendBranchResult`. To see if the push succeeded, check the `LastPushResult` property, which is a `Git.Automation.PushResult` enumeration. A value of `Ok` means the push succeeded. Other values are `Failed` or `Rejected`. The result object contains lists for every merge and push operation this function attempts. Merge results are in a `MergeResult` object, from first attempt to most recent attempt. Push results are in a `PushResult` property, from first attempt to most recent attempt. The most recent merge result is available as the `LastMergeResult` property. The most recent push result is available as the `LastPushResult` property. This command implements the `git push` command, and, if there are new changes in the remote repository, the `git pull` command. .LINK Sync-GitBranch .LINK Send-GitCommit .EXAMPLE Send-GitBranch Demonstrates how to push changes to a remote repository. #> [CmdletBinding()] [OutputType([Git.Automation.SendBranchResult])] param( [string] $RepoRoot = (Get-Location).ProviderPath, [ValidateSet('FastForward','Merge')] [string] # What to do when merging remote changes into your local branch. By default, will use your configured `merge.ff` configuration options. Set to `Merge` to always create a merge commit. Use `FastForward` to only allow fast-forward "merges" (i.e. move the remote branch to point to your local branch head if there are no new changes on the remote branch). When automating, the safest option is `Merge`. If you choose `FastForward` and the remote branch has new changes on it, this function will fail. $MergeStrategy, [int] # The number of times to retry the push. Default is 5. $Retry = 5 ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $mergeStrategyParam = @{ } if( $MergeStrategy ) { $mergeStrategyParam['MergeStrategy'] = $MergeStrategy } $result = New-Object -TypeName 'Git.Automation.SendBranchResult' try { $tryNum = 0 do { $syncResult = Sync-GitBranch -RepoRoot $RepoRoot @mergeStrategyParam if( -not $syncResult ) { return } $result.MergeResult.Add($syncResult) if( $syncResult.Status -eq [LibGit2Sharp.MergeStatus]::Conflicts ) { Write-Error -Message ('There are merge conflicts pulling remote changes into local branch.') return } $pushResult = Send-GitCommit -RepoRoot $RepoRoot $result.PushResult.Add($pushResult) } while( $tryNum++ -lt $Retry -and $pushResult -ne [Git.Automation.PushResult]::Ok ) } finally { Write-Output -InputObject $result -NoEnumerate } } function Send-GitCommit { <# .SYNOPSIS Pushes commits from the current Git repository to its remote source repository. .DESCRIPTION The `Send-GitCommit` function sends all commits on the current branch of the local Git repository to its upstream remote repository. If you are pushing a new branch, use the `SetUpstream` switch to ensure Git tracks the new remote branch as a copy of the local branch. If the repository requires authentication, pass the username/password via the `Credential` parameter. Returns a `Git.Automation.PushResult` that represents the result of the push. One of: * `Ok`: the push succeeded * `Failed`: the push failed. * `Rejected`: the push failed because there are changes on the branch that aren't present in the local repository. They should get pulled into the local repository and the push attempted again. This function implements the `git push` command. .EXAMPLE Send-GitCommit Pushes commits from the repository at the current location to its upstream remote repository .EXAMPLE Send-GitCommit -RepoRoot 'C:\Build\TestGitRepo' -Credential $PsCredential Pushes commits from the repository located at 'C:\Build\TestGitRepo' to its remote using authentication #> [CmdletBinding()] [OutputType([Git.Automation.PushResult])] param( [string] # Specifies the location of the repository to synchronize. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [pscredential] # The credentials to use to connect to the source repository. $Credential, [Switch] # Add tracking information for any new branches pushed so Git sees the local branch and remote branch as the same. $SetUpstream ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot -Verify try { [LibGit2Sharp.Branch]$currentBranch = $repo.Branches | Where-Object { $_.IsCurrentRepositoryHead -eq $true } $result = Send-GitObject -RefSpec $currentBranch.CanonicalName -RepoRoot $RepoRoot -Credential $Credential if( -not $SetUpstream -or $result -ne [Git.Automation.PushResult]::Ok ) { return $result } # Setup tracking with the new remote branch. [void]$repo.Branches.Update($currentBranch, { param( [LibGit2Sharp.BranchUpdater] $Updater ) $updater.Remote = 'origin' $updater.UpstreamBranch = $currentBranch.CanonicalName }); return $result } finally { $repo.Dispose() } } function Send-GitObject { <# .SYNOPSIS Sends Git refs and object to a remote repository. .DESCRIPTION The `Send-GitObject` functions sends objects from a local repository to a remote repository. You specify what refs and objects to send with the `Revision` parameter. This command implements the `git push` command. .EXAMPLE Send-GitObject -Revision 'refs/heads/master' Demonstrates how to push the commits on a specific branch to the default remote repository. .EXAMPLE Send-GitObject -Revision 'refs/heads/master' -RemoteName 'upstream' Demonstrates how to push an object (in this case, the master branch) to a specific remote repository, in this case the remote named "upstream". .EXAMPLE Send-GitObject -Revision 'refs/tags/4.45.6' Demonstrates how to push a tag to the default remote repository. .EXAMPLE Send-GitObject -Tags Demonstrates how to push all tags to the default remote repository. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,ParameterSetName='ByRefSpec')] [string[]] # The refs that should be pushed to the remote repository. $RefSpec, [Parameter(Mandatory=$true,ParameterSetname='Tags')] [Switch] # Push all tags to the remote repository. $Tags, [string] # The name of the remote repository to send the changes to. The default is `origin`. $RemoteName = 'origin', [string] # The path to the local repository from which to push changes. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [pscredential] # The credentials to use to connect to the source repository. $Credential ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot -Verify $pushOptions = New-Object -TypeName 'LibGit2Sharp.PushOptions' if( $Credential ) { $gitCredential = New-Object -TypeName 'LibGit2Sharp.SecureUsernamePasswordCredentials' $gitCredential.Username = $Credential.UserName $gitCredential.Password = $Credential.Password $pushOptions.CredentialsProvider = { return $gitCredential } } $remote = $repo.Network.Remotes | Where-Object { $_.Name -eq $RemoteName } if( -not $remote ) { Write-Error -Message ('A remote named "{0}" does not exist.' -f $RemoteName) return [Git.Automation.PushResult]::Failed } if( $Tags ) { $RefSpec = $repo.Tags | ForEach-Object { $_.CanonicalName } } try { $repo.Network.Push($remote, $RefSpec, $pushOptions) return [Git.Automation.PushResult]::Ok } catch { Write-Error -ErrorRecord $_ switch ( $_.FullyQualifiedErrorId ) { 'NonFastForwardException' { return [Git.Automation.PushResult]::Rejected } 'LibGit2SharpException' { return [Git.Automation.PushResult]::Failed } 'BareRepositoryException' { return [Git.Automation.PushResult]::Failed } default { return [Git.Automation.PushResult]::Failed } } } finally { $repo.Dispose() } } function Set-GitConfiguration { <# .SYNOPSIS Sets Git configuration options .DESCRIPTION The `Set-GitConfiguration` function sets Git configuration variables. These variables change Git's behavior. Git has hundreds of variables and we can't document them here. Some are shared between Git commands. Some variables are only used by specific commands. The `git help config` help topic lists most of them. By default, this function sets options for the current repository, or a specific repository using the `RepoRoot` parameter. To set options for the current user across all repositories, use the `-Global` switch. If running in an elevated process, `Set-GitConfiguration` will look in `$env:HOME` and `$env:USERPROFILE` (in that order) for a .gitconfig file. If it can't find one, it will create one in `$env:HOME`. If the `HOME` environment variable isn't defined, it will create a `.gitconfig` file in the `$env:USERPROFILE` directory. If running in a non-elevated process, `Set-GitConfiguration` will look in `$env:HOME`, `$env:HOMEDRIVE$env:HOMEPATH`, and `$env:USERPROFILE` (in that order) and use the first `.gitconfig` file it finds. If it can't find a `.gitconfig` file, it will create a `.gitconfig` in the `$env:HOME` directory. If the `HOME` environment variable isn't defined, it will create the `.gitconfig` file in the `$env:HOMEDRIVE$env:HOMEPATH` directory. To set the configuration in a specific file, use the `Path` parameter. If the file doesn't exist, it is created. This function implements the `git config` command. .EXAMPLE Set-GitConfiguration -Name 'core.autocrlf' -Value 'false' Demonstrates how to set the `core.autocrlf` setting to `false` for the repository in the current directory. .EXAMPLE Set-GitConfiguration -Name 'core.autocrlf' -Value 'false' -Global Demonstrates how to set a configuration variable so that it applies across all a user's repositories by using the `-Global` switch. .EXAMPLE Set-GitConfiguration -Name 'core.autocrlf' -Value 'false' -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to set a configuration variable for a specific repository. In this case, the configuration for the repository at `C:\Projects\GitAutomation` will be updated. #> [CmdletBinding()] param( [Parameter(Mandatory=$true,Position=0)] [string] # The name of the configuration variable to set. $Name, [Parameter(Mandatory=$true,Position=1)] [string] # The value of the configuration variable. $Value, [Parameter(ParameterSetName='ByScope')] [LibGit2Sharp.ConfigurationLevel] # Where to set the configuration value. Local means the value will be set for a specific repository. Global means set for the current user. System means set for all users on the current computer. The default is `Local`. $Scope = ([LibGit2Sharp.ConfigurationLevel]::Local), [Parameter(Mandatory=$true,ParameterSetName='ByPath')] [string] # The path to a specific file whose configuration to update. $Path, [Parameter(ParameterSetName='ByScope')] [string] # The path to the repository whose configuration variables to set. Defaults to the repository the current directory is in. $RepoRoot ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $PSCmdlet.ParameterSetName -eq 'ByPath' ) { if( -not (Test-Path -Path $Path -PathType Leaf) ) { New-Item -Path $Path -ItemType 'File' -Force | Write-Verbose } $Path = Resolve-Path -Path $Path | Select-Object -ExpandProperty 'ProviderPath' $config = [LibGit2Sharp.Configuration]::BuildFrom($Path) try { $config.Set( $Name, $Value, 'Local' ) } finally { $config.Dispose() } return } $pathParam = @{} if( $RepoRoot ) { $pathParam['Path'] = $RepoRoot } if( $Scope -eq [LibGit2Sharp.ConfigurationLevel]::Local ) { $repo = Find-GitRepository @pathParam -Verify if( -not $repo ) { return } try { $repo.Config.Set($Name,$Value,$Scope) } finally { $repo.Dispose() } } else { Update-GitConfigurationSearchPath -Scope $Scope $configFileName = 'config' $configFileNames = @{ [LibGit2Sharp.ConfigurationLevel]::System = 'gitconfig' [LibGit2Sharp.ConfigurationLevel]::Global = '.gitconfig' } if( $configFileNames.ContainsKey($Scope) ) { $configFileName = $configFileNames[$Scope] } if( $Scope -eq [LibGit2Sharp.ConfigurationLevel]::Xdg ) { $xdgConfigPath = Join-Path -Path $env:HOME -ChildPath '.config\git\config' if( -not (Test-Path -Path $xdgConfigPath -PathType Leaf) ) { New-Item -Path $xdgConfigPath -ItemType 'File' -Force | Out-Null } } # LibGit2 only creates config files explicitly. [string[]]$searchPaths = [LibGit2Sharp.GlobalSettings]::GetConfigSearchPaths($Scope) | Join-Path -ChildPath $configFileName if( $searchPaths ) { $scopeConfigFiles = $searchPaths | Where-Object { Test-Path -Path $_ -PathType Leaf } if( -not $scopeConfigFiles ) { New-Item -Path $searchPaths[0] -ItemType 'File' -Force | Write-Verbose } } $config = [LibGit2Sharp.Configuration]::BuildFrom([nullstring]::Value,[nullstring]::Value) try { $config.Set($Name,$Value,$Scope) } finally { $config.Dispose() } } } function Sync-GitBranch { <# .SYNOPSIS Updates the current branch so it is in sync with its remote branch. .DESCRIPTION The `Sync-GitBranch` function merges in commits from the current branch's remote branch. It pulls in these commits from the remote repository. If there are new commits in the remote branch, they are merged into your current branch and a new commit is created. If there are no new commits in the remote branch, the remote branch is updated to point to the head of your current branch. This is called a "fast forward" merge. This function's default behavior is controlled by Git's `merge.ff` setting. If unset or set to `true`, it behaves as described above. You can also use the `MergeStrategy` parameter to control how you want remote commits to get merged into your branch. If the `merge.ff` setting is `only`, or you pass `FastForward` to the `MergeStrategy` parameter, this function will only do a fast-forward merge. If there are new commits in the remote branch, a fast-forward merge is impossible and this function will fail. If the `merge.ff` setting is `false`, or you pass `Merge` to the `MergeStrategy` parameter, the function will always create a merge commit, even if there are no new commits on the remote branch. Returns a `LibGit2Sharp.MergeResult` object, which has two properties: * `Commit`: the merge commit created, if any. * `Status`: the status of the merge. One of: * `UpToDate`: there were no new changes on the remote branch to bring in. In this case, `Commit` will be empty. * `FastForward`: the merge was fast-forwarded. In this case, `Commit` will be emtpy. * `NonFastForward`: a new merge commit was created. In this case, `Commit` will be the commit object created`. * `Conflicts`: merging in the remote branch resulted in merge conflicts. You'll need to do extra processing to resolve the conflicts. If the function needs to create a merge commit, but the `merge.ff` option is `only` or the `MergeStrategy` parameter is `FastForward`, the function will write an error and return `$null`. If there are conflicts made during the merge, this function won't write an error. You need to check the return object to ensure there are no conflicts. If the current branch isn't tracking a remote branch, this function will look for a remote branch with the same name, and create tracking information. If there is no remote branch with the same name, this function will write an error and return `$null`. By default, this function works on the repository in the current directory. Use the `RepoRoot` parameter to specify an explicit repository. This function implements the `git pull` command. .EXAMPLE Sync-GitBranch Demonstrates the simplest way to get your current branch up-to-date with its remote branch. .EXAMPLE Sync-GitBranch -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to pull remotes commits for a repository that isn't in the current directory. #> [CmdletBinding()] [OutputType([LibGit2Sharp.MergeResult])] param( [string] # The repository to fetch updates for. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [ValidateSet('FastForward','Merge')] [string] # What to do when merging remote changes into your local branch. By default, will use your configured `merge.ff` configuration options. Set to `Merge` to always create a merge commit. Use `FastForward` to only allow fast-forward "merges" (i.e. move the remote branch to point to your local branch head if there are no new changes on the remote branch). When automating, the safest option is `Merge`. If you choose `FastForward` and the remote branch has new changes on it, this function will fail. $MergeStrategy ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { $branch = $repo.Branches | Where-Object { $_.IsCurrentRepositoryHead } if( -not $branch ) { Write-Error -Message ('Repository in "{0}" isn''t on a branch. Use "Update-GitRepository" to update to a branch.' -f $RepoRoot) return } if( -not $branch.IsTracking ) { [LibGit2Sharp.Branch]$remoteBranch = $repo.Branches | Where-Object { $_.UpstreamBranchCanonicalName -eq $branch.CanonicalName } if( -not $remoteBranch ) { Write-Error -Message ('Branch "{0}" in repository "{1}" isn''t tracking a remote branch and we''re unable to find a remote branch named "{0}".' -f $branch.FriendlyName,$RepoRoot) return } [void]$repo.Branches.Update($branch, { param( [LibGit2Sharp.BranchUpdater] $Updater ) $Updater.TrackedBranch = $remoteBranch.CanonicalName }) } $pullOptions = New-Object LibGit2Sharp.PullOptions $mergeOptions = New-Object LibGit2Sharp.MergeOptions $mergeOptions.FastForwardStrategy = [LibGit2Sharp.FastForwardStrategy]::Default if( $MergeStrategy -eq 'FastForward' ) { $mergeOptions.FastForwardStrategy = [LibGit2Sharp.FastForwardStrategy]::FastForwardOnly } elseif( $MergeStrategy -eq 'Merge' ) { $mergeOptions.FastForwardStrategy = [LibGit2Sharp.FastForwardStrategy]::NoFastForward } $pullOptions.MergeOptions = $mergeOptions $signature = New-GitSignature -RepoRoot $RepoRoot try { [LibGit2Sharp.Commands]::Pull($repo, $signature, $pullOptions) } catch { Write-Error -ErrorRecord $_ } } finally { $repo.Dispose() } } function Test-GitBranch { <# .SYNOPSIS Checks if a branch exists in a Git repository. .DESCRIPTION The `Test-GitBranch` command tests if a branch exists in a Git repository. It returns $true if a branch exists; $false otherwise. Pass the branch name to test to the `Name` parameter .EXAMPLE Test-GitBranch -Name 'develop' Demonstrates how to check if the 'develop' branch exists in the current directory. .EXAMPLE Test-GitBranch -RepoRoot 'C:\Projects\GitAutomation' -Name 'develop' Demonstrates how to check if the 'develop' branch exists in a specific repository. #> [CmdletBinding()] param( [string] # Specifies which git repository to check. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true)] [string] # The name of the branch. $Name ) Set-StrictMode -Version 'Latest' $branch = Get-GitBranch -RepoRoot $RepoRoot | Where-Object { $_.Name -ceq $Name } if( $branch ) { return $true } else { return $false } } function Test-GitCommit { <# .SYNOPSIS Tests if a Git revision exists. .DESCRIPTION The `Test-GitCommit` function tests if a commit exists. You pass the revision you want to check to the `Revision` parameter and the repository in the current working directory is checked. If a commit exists, the function returns `$true`. Otherwise, it returns `$false`. To test for a commit in a specific repository, pass the path to that repository to the `RepoRoot` parameter. .EXAMPLE Test-GitCommit -Revision 'feature/test-gitcommit' Demonstrates how to check if a branch exists. In this example, if the branch `feature/test-gitcommit` exists, `$true` is returned. .EXAMPLE Test-GitCommit -Revision 'deadbee' Demonstrates how to check if a commit exists using its partial SHA hash. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory=$true)] [string] # A revision to test, e.g. a branch name, partial commit SHA hash, full commit SHA hash, tag name, etc. # # See https://git-scm.com/docs/gitrevisions for documentation on how to specify Git revisions. $Revision, [string] # The path to the repository. Defaults to the current directory. $RepoRoot ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( (Get-GitCommit -Revision $Revision -RepoRoot $RepoRoot -ErrorAction Ignore) ) { return $true } return $false } function Test-GitRemoteUri { <# .SYNOPSIS Tests if the uri leads to a git repository .DESCRIPTION The `Test-GitRemoteUri` tries to list remote references for the specified uri. A uri that is not a git repo will throw a LibGit2SharpException. This function is similar to `git ls-remote` but returns a bool based on if there is any output .EXAMPLE Test-GitRemoteUri -Uri 'ssh://git@stash.portal.webmd.com:7999/whs/blah.git' Demonstrates how to check if there is a repo at the specified uri #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] # The uri to test $Uri ) Set-StrictMode -Version 'Latest' try { [LibGit2Sharp.Repository]::ListRemoteReferences($Uri) | Out-Null } catch [LibGit2Sharp.LibGit2SharpException] { return $false } return $true } function Test-GitTag { <# .SYNOPSIS Tests if a tag exists in a Git repository. .DESCRIPTION The `Test-GitTag function tests if a tag exists in a Git repository. If a tag exists, returns $true; otherwise $false. Pass the name of the tag to check for to the `Name` parameter. .EXAMPLE Test-GitTag -Name 'Hello' Demonstrates how to check if the tag 'Hello' exists in the current directory. #> [CmdletBinding()] param( [string] # Specifies which git repository to check. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [Parameter(Mandatory=$true)] [string] # The name of the tag to check for. $Name ) Set-StrictMode -Version 'Latest' $tag = Get-GitTag -RepoRoot $RepoRoot -Name $Name | Where-Object { $_.Name -eq $Name } return ($tag -ne $null) } function Test-GitUncommittedChange { <# .SYNOPSIS Tests for uncommitted changes in a git repository. .DESCRIPTION The `Test-GitUncommittedChange` function checks for any uncommited changes in a git repository. It defaults to the current repository and only the current branch. Use the `RepoRoot` parameter to specify an explicit path to another repo. Implements the `git diff --exit-code` command ( No output if no uncommitted changes, otherwise output diff ) .EXAMPLE Test-GitUncommittedChange -RepoRoot 'C:\Projects\GitAutomation' Demonstrates how to check for uncommitted changes in a repository that isn't the current directory. #> [CmdletBinding()] param( [string] # The repository to check for uncommitted changes. Defaults to current directory $RepoRoot = (Get-Location).ProviderPath ) Set-StrictMode -Version 'Latest' if( Get-GitRepositoryStatus -RepoRoot $RepoRoot ) { return $true } return $false } function Update-GitConfigurationSearchPath { [CmdletBinding()] param( [LibGit2Sharp.ConfigurationLevel] # The scope of the configuration. Nothing is updated unless `Global` is used. $Scope ) Set-StrictMode -Version 'Latest' if( $Scope -ne [LibGit2Sharp.ConfigurationLevel]::Global ) { return } if( -not (Test-Path -Path 'env:HOME') ) { return } $homePath = Get-Item -Path 'env:HOME' | Select-Object -ExpandProperty 'Value' $homePath = $homePath -replace '\\','/' [string[]]$searchPaths = [LibGit2Sharp.GlobalSettings]::GetConfigSearchPaths($Scope) if( $searchPaths[0] -eq $homePath ) { return } $searchList = New-Object -TypeName 'Collections.Generic.List[string]' $searchList.Add($homePath) $searchList.AddRange($searchPaths) [LibGit2Sharp.GlobalSettings]::SetConfigSearchPaths($Scope, $searchList.ToArray()) } function Update-GitRepository { <# .SYNOPSIS Updates the working directory of a Git repository to a specific commit. .DESCRIPTION The `Update-GitRepository` function updates a Git repository to a specific commit, i.e. it checks out a specific commit. The default target is "HEAD". Use the `Revision` parameter to specifiy a different branch, tag, commit, etc. If you specify a branch name, and there isn't a local branch by that name, but there is a remote branch, this function creates a new local branch that tracks the remote branch. It defaults to the current repository. Use the `RepoRoot` parameter to specify an explicit path to another repo. Use the `Force` switch to remove any uncommitted/unstaged changes during the checkout. Otherwise, the update will fail. This function implements the `git checkout <target>` command. .EXAMPLE Update-GitRepository -RepoRoot 'C:\Projects\GitAutomation' -Revision 'feature/ticket' Demonstrates how to checkout the 'feature/ticket' branch of the given repository. .EXAMPLE Update-GitRepository -RepoRoot 'C:\Projects\GitAutomation' -Revision 'refs/tags/tag1' Demonstrates how to create a detached head at the tag 'tag1'. .EXAMPLE Update-GitRepository -RepoRoot 'C:\Projects\GitAutomation' -Revision 'develop' -Force Demonstrates how to remove any uncommitted changes during the checkout by using the `Force` switch. #> [CmdletBinding()] param( [string] # Specifies which git repository to update. Defaults to the current directory. $RepoRoot = (Get-Location).ProviderPath, [string] # The revision checkout, i.e. update the repository to. A revision can be a specific commit ID/sha (short or long), branch name, tag name, etc. Run git help gitrevisions or go to https://git-scm.com/docs/gitrevisions for full documentation on Git's revision syntax. $Revision = "HEAD", [Switch] # Remove any uncommitted changes when checking out/updating to `Revision`. $Force ) Set-StrictMode -Version 'Latest' $repo = Find-GitRepository -Path $RepoRoot -Verify if( -not $repo ) { return } try { $checkoutOptions = New-Object -TypeName 'LibGit2Sharp.CheckoutOptions' if( $Force ) { $checkoutOptions.CheckoutModifiers = [LibGit2Sharp.CheckoutModifiers]::Force } $branch = $repo.Branches[$Revision] if( -not $branch ) { [LibGit2Sharp.Branch]$remoteBranch = $repo.Branches | Where-Object { $_.UpstreamBranchCanonicalName -eq ('refs/heads/{0}' -f $Revision) } if( $remoteBranch ) { $branch = $repo.Branches.Add($Revision, $remoteBranch.Tip.Sha) $repo.Branches.Update($branch, { param( [LibGit2Sharp.BranchUpdater] $Updater ) $Updater.TrackedBranch = $remoteBranch.CanonicalName }) } } [LibGit2Sharp.Commands]::Checkout($repo, $Revision, $checkoutOptions) } finally { $repo.Dispose() } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every function/cmdlet call in your function. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. $Cmdlet, [Parameter(Mandatory = $true)] [Management.Automation.SessionState] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. $SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |