Skip to content

Instantly share code, notes, and snippets.

@nbarnwell
Created December 9, 2025 09:12
Show Gist options
  • Select an option

  • Save nbarnwell/c4b099c0d39fcafb4773cc9a67731181 to your computer and use it in GitHub Desktop.

Select an option

Save nbarnwell/c4b099c0d39fcafb4773cc9a67731181 to your computer and use it in GitHub Desktop.
A simple PowerShell script that allows fast iterative experimentation to help understand Git behaviours
function Invoke-CommandAtLocation {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $WorkingDirectory,
[Parameter(Mandatory)]
[scriptblock] $Command
)
process {
Push-Location $WorkingDirectory
try {
& $Command
} finally {
Pop-Location
}
}
}
function New-TestRepo {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[string[]] $RepoName,
[string] $Path = (Get-Location),
[string] $Username = "TestUser",
[switch] $Bare
)
process {
$RepoName |
Foreach-Object {
$currentName = $_
mkdir (Join-Path $Path $currentName)
Invoke-CommandAtLocation -WorkingDirectory $currentName -Command {
$gitArgs = @("init")
if ($Bare) {
$gitArgs += "--bare"
}
Write-Host "git $gitArgs"
Start-Process git -ArgumentList $gitArgs -NoNewWindow -Wait
}
}
}
}
function New-TestRepoCommit {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[string] $Content = ($RepoName + "." + [DateTime]::Now.ToString("O")),
[Parameter(Mandatory)]
[string] $Message,
[string] $Filename = "Content.txt",
[string] $Path = (Get-Location)
)
process {
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
$Content | Out-File $Filename -Append
git add $Filename
git commit -m $Message
}
}
}
function New-TestRepoBranch {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[Parameter(Mandatory)]
[string] $BranchName,
[string] $Path = (Get-Location),
[switch] $Checkout
)
process {
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
git branch $BranchName
if ($Checkout) {
Set-CurrentBranch -RepoName $RepoName -BranchName $BranchName -Path $Path
}
}
}
}
function Set-GitRepoUsername {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[Parameter(Mandatory)]
[string] $Username,
[string] $Path = (Get-Location)
)
process {
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
git config user.name $Username
git config user.email "$Username@example.com"
}
}
}
function Set-CurrentBranch {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[Parameter(Mandatory)]
[string] $BranchName,
[string] $Path = (Get-Location)
)
process {
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
git checkout $BranchName
}
}
}
function Invoke-GitReset {
[CmdletBinding(DefaultParameterSetName = 'ByCommitHash')]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[Parameter(Mandatory)]
[ValidateSet("Hard", "Soft", "Mixed")]
[string] $Mode,
[Parameter(Mandatory, ParameterSetName = 'ByCommitHash')]
[string] $CommitHash,
[Parameter(Mandatory, ParameterSetName = 'ByBranchName')]
[string] $BranchName,
[Parameter(Mandatory, ParameterSetName = 'ByRelativeRef')]
[string] $RelativeBase,
[Parameter(Mandatory, ParameterSetName = 'ByRelativeRef')]
[int] $RelativeOffset,
[Parameter(Mandatory, ParameterSetName = 'ByReflog')]
[string] $ReflogBase,
[Parameter(Mandatory, ParameterSetName = 'ByReflog')]
[int] $ReflogIndex,
[string] $Path = (Get-Location)
)
$modeLower = $Mode.ToLower()
switch ($PSCmdlet.ParameterSetName) {
'ByCommitHash' {
$revset = $CommitHash
}
'ByBranchName' {
$revset = $BranchName
}
'ByRelativeRef' {
$revset = "$RelativeBase~$RelativeOffset"
}
'ByReflog' {
$revset = "$ReflogBase@{$ReflogIndex}"
}
}
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
$command = "git reset --$modeLower $revset"
Write-Host "$command"
Invoke-Expression $command
}
}
function Receive-Change {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[string[]] $Destination,
[string] $Source = "Server",
[string] $Path = (Get-Location),
[switch] $Rebase
)
process {
$Destination |
Foreach-Object {
$currentDestination = $_
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $currentDestination) -Command {
$gitArgs = @( "pull" )
if ($Rebase) {
$gitArgs += "--rebase"
}
git $gitArgs
}
}
}
}
function Invoke-GitRebase {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $RepoName,
[Parameter(Mandatory)]
[string] $Upstream,
[string] $Path = (Get-Location)
)
process {
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $RepoName) -Command {
git rebase $Upstream
}
}
}
function Send-Change {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[string[]] $Source,
[string] $Path = (Get-Location),
[switch] $IncludeTags,
[switch] $Force
)
process {
$Source |
Foreach-Object {
$currentSource = $_
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path $currentSource) -Command {
$gitArgs = @( "push" )
if ($IncludeTags) {
$gitArgs += "--tags"
}
if ($Force) {
$gitArgs += "--force"
}
git $gitArgs
}
}
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $Path "ServerInspectionHatch") -Command {
$gitArgs = @( "pull" )
if ($IncludeTags) {
$gitArgs += "--tags"
}
git $gitArgs
}
}
}
function Remove-ObsoleteTag {
# Fetch latest tags from remote
git fetch --tags
# Get all local tags
$localTags = git tag | ForEach-Object { $_.Trim() }
foreach ($tag in $localTags) {
# Check if tag exists on remote
$existsOnRemote = git ls-remote --tags origin | Select-String "refs/tags/$tag`$"
if (-not $existsOnRemote) {
Write-Host "Deleting local tag: $tag"
git tag -d $tag
}
}
}
function New-SemverTag {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(ParameterSetName = "BreakingChange")]
[switch] $BreakingChange = $false,
[Parameter(ParameterSetName = "NewFeature")]
[switch] $NewFeature = $false,
[Parameter(ParameterSetName = "BugFix")]
[switch] $BugFix = $false)
$version = git describe --tags --long --match "v*.*.*" --abbrev=40
($tag, $tagDistance, $revisionUID) = $version.Split('-')
$tag -match '^[vV](\d+)\.(\d+)\.(\d+)$' | out-null
if ($Matches.count -eq 0) {
throw "No version tag found on repository."
}
([int]$Major, [int]$Minor, [int]$Build) = $Matches[1..3]
$oldTag = 'v{0}.{1}.{2}' -f $Major, $Minor, $Build
if ($BreakingChange) {
$Major += 1;
$Minor = 0;
$Build = 0;
}
if ($NewFeature) {
$Minor += 1;
$Build = 0;
}
if ($BugFix) {
$Build += 1;
}
$newTag = 'v{0}.{1}.{2}' -f $Major, $Minor, $Build
$message = "Moving from $oldTag to $newTag"
if ($PsCmdlet.ShouldProcess($message)) {
Write-Host $message
git tag $NewTag
}
}
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
cd 'C:\Temp'
$testPath = Join-Path (Get-Location) "RedditQuestionTest1"
Write-Host "Using test path: $testPath"
if (Test-Path $testPath) {
Remove-Item $testPath -Force -Recurse
}
mkdir $testPath -Force
cd $testPath
$repoName = "Sincere-Melody324"
New-TestRepo -RepoName $repoName
Set-GitRepoUsername -RepoName $repoName -Username $repoName
New-TestRepoCommit -RepoName $repoName -Filename "Hi.txt" -Message "M1"
New-TestRepoCommit -RepoName $repoName -Filename "Hi.txt" -Message "M2"
New-TestRepoBranch -RepoName $repoName -BranchName "A" -Checkout
New-TestRepoCommit -RepoName $repoName -Filename "A.txt" -Message "A1"
New-TestRepoBranch -RepoName $repoName -BranchName "B" -Checkout
New-TestRepoCommit -RepoName $repoName -Filename "B.txt" -Message "B1"
New-TestRepoCommit -RepoName $repoName -Filename "B.txt" -Message "B2"
New-TestRepoCommit -RepoName $repoName -Filename "B.txt" -Message "B3"
Set-CurrentBranch -RepoName $repoName -BranchName "A"
New-TestRepoCommit -RepoName $repoName -Filename "A.txt" -Message "A2"
Set-CurrentBranch -RepoName $repoName -BranchName "B"
Invoke-GitRebase -RepoName $repoName -Upstream "A"
<# New-TestRepo -RepoName "Server" -Bare
Invoke-CommandAtLocation -WorkingDirectory $testPath -Command {
git clone "Server" Client2
git clone "Server" Client2
git clone "Server" ServerInspectionHatch
}
New-TestRepoCommit -RepoName "Client1" -Filename "Base.txt" -Message "Initial commit from Client1"
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $testPath "Client1") -Command {
git tag v0.0.0
}
Send-Change -Source "Client1" -IncludeTags
Receive-Change -Destination "Client2" -Rebase
# Now the test starts
New-TestRepoCommit -RepoName "Client1" -Filename "Client1.txt" -Message "Second commit from Client1"
Send-Change -Source "Client1"
New-TestRepoCommit -RepoName "Client2" -Filename "Client2.txt" -Message "First commit from Client2"
Receive-Change -Destination "Client2" -Rebase
Send-Change -Source "Client2"
New-TestRepoCommit -RepoName "Client1" -Filename "Client1.txt" -Message "Third commit from Client1"
Invoke-CommandAtLocation -WorkingDirectory (Join-Path $testPath "Client1") -Command {
New-SemverTag -NewFeature
} #>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment