Functions/Sync-HgRepository.ps1
function Sync-HgRepository { <# .SYNOPSIS Synchronizes the current Mercurial repository with its remote source repository. .DESCRIPTION The process of pushing your changes to a remote repository usually goes something like this: PS> hg commit -m "My change are done! Woot!" PS> hg push abort: push creates new remote head addbadbeddad PS> hg pull PS> hg merge PS> hg commit -m "Merge" PS> hg push And sometimes you have to repeat the pull/merge/commit/push cycle multiple times. This script combines all these operations. As long as a merge doesn't make any new, local modifications, the pull/merge/commit/push cycle will repeat until the push succeeds. The script attempts to push. If the push fails because there are incoming changes, it pulls those changes down, merges them, commits the merge (if there were no conflicts or merged files), then tries to push again. It repeats until the push succeeds or fails and there are no incoming changes. If the merge results in any conflicts or merged files, the script stops so you can resolve the conflicts and review the merged files. Once you commit the merge, you can re-run Sync-HgRepository.ps1. If a pull brings in multiple heads in another branch, the script will stop and you�ll have to go to that branch, merge those heads, then re-run Sync-HgRepository.ps1. Returns the output of the the Mercurial push commands it runs. If the synchronization succeeds, sets the `$PsHg?` variable to `$true`. Otherwise, it sets it to `$false`. ALIASES syhg, hgsync .OUTPUTS PsHg.SyncResult .EXAMPLE PS> Sync-HgRepository The local repository in the current directory will be synchronized with its remote repository. .EXAMPLE PS> Sync-HgRepository -RepoRoot C:\Projects\PsHg The repository at `C:\Projects\PsHg` will be synchronized with its remote repository. #> [CmdletBinding()] [OutputType([string[]])] param( [Switch] # The push is creating a new branch. Adds the `--new-branch` argument to the `hg push` command. $NewBranch, [string] # The bookmark to synchronize. Only this boookmark and changesets before it are pushed. $Bookmark, [Switch] # Forces the push, even if it will create multiple heads. You usually don't want to do this. Adds the `-f` argument to the `hg push` command. $Force, [Switch] # The synchronization is running in non-interactive mode. When set, ensures Mercurial doesn't prompt for anything. If Mercurial does need to prompt, it will fail instead. $NonInteractive, [string] # Specifies an explicit repository to push to. $Destination, [string] # The path to the repository that should by synced. Defaults to the current directory. $RepoRoot = (Resolve-Path .) ) $rRepoRoot = Resolve-HgRoot -Path $RepoRoot if( -not $rRepoRoot ) { return } Write-Verbose ('Working in {0}.' -f $rRepoRoot) Push-Location $rRepoRoot try { $currentBranch = Get-HgBranch -Current $revision = $currentBranch.Name if( $Bookmark ) { $revision = $Bookmark } $sendArgs = @{ } $receiveArgs = @{ } if( $NewBranch ) { $sendArgs.NewBranch = $true } if ( $Bookmark ) { $receiveArgs.Bookmark = $Bookmark $sendArgs.Bookmark = $Bookmark } if ( $Destination ) { $sendArgs.Destination = $Destination $receiveArgs.Source = $Destination } $forceArg = '' if ( $Force ) { $forceArg = '-f' } do { if( -not $Bookmark -and (Test-HgHead -Revision $currentBranch.Name) -and -not $Force ) { Write-Error ('Unable to push: found multiple local heads. Run `hg heads .` to view, `hg merge` to merge.') break } Write-Verbose ('Pushing changesets') $sendArgs | Format-Table | Out-String | Write-Verbose $errCount = $Global:Error.Count Send-HgChangeset @sendArgs -ErrorAction SilentlyContinue -NonInteractive:$NonInteractive if( $PsHg? ) { break } if( -not (Test-HgIncomingChangeset -Revision $revision -Source $Destination) ) { $currentErrCount = $Global:Error.Count if( $currentErrCount -gt $errCount ) { $errorsToReport = New-Object 'Collections.ArrayList' for( $idx = ($currentErrCount - $errCount - 1); $idx -ge 0; --$idx ) { [void]$errorsToReport.Add( $Global:Error[$idx] ) $Global:Error.RemoveAt($idx) } foreach( $err in $errorsToReport ) { Write-Error -ErrorRecord $err } } else { Write-Error ('Unable to push: an unknown error occurred. LastExitCode: {0}' -f $LastExitCode) } break } else { # Remove the push errors, because we're handling them. while( $Global:Error.Count -gt $errCount ) { $Global:Error.RemoveAt( 0 ) } } $incoming = Get-HgIncomingChangeset -Revision $revision -Source $Destination | Select-Object -Last 1 Write-Verbose ('Incoming changes. Updating to revision {0}.' -f $incoming.Node) $receiveArgs.Revision = $incoming.Node Receive-HgChangeset @receiveArgs if( -not $PsHg? ) { Write-Verbose ('Pull failed.') break } Write-Verbose ('Merging with revision {0}.' -f $incoming.Node) Merge-HgChangeset -Revision $incoming.Node -NonInteractive:$NonInteractive $mergeResult = Get-HgMergeResult if( -not $mergeResult ) { $Script:PsHg? = $false break } if( $mergeResult.FilesMerged -gt 0 ) { Write-Warning ('{0} files merged. Please review the merge, then commit and re-run synchronization.' -f $mergeResult.FilesMerged) $Script:PsHg? = $false break } if( $mergeResult.FilesUnresolved -gt 0 ) { Write-Error ('{0} files unresolved.' -f $mergeResult.FilesUnresolved) $Script:PsHg? = $false break } if( -not $Destination ) { $Destination = @( 'default-push', 'default' ) | ForEach-Object { hg showconfig ('paths.{0}' -f $_) } | Select-Object -First 1 } if( $Destination ) { $destinationMsg = ' with {0}' -f $Destination } $merge = Save-HgChangeset -Message ('Automated merge{0} by {1}\{2} on {3}.' -f $destinationMsg,$env:userdomain,$env:username,$env:computername) if( -not $merge ) { Write-Verbose ('Saving merge failed.') break } $merge } while( $true ) } finally { Pop-Location } } Set-Alias -Name 'hgsync' -Value 'Sync-HgRepository' Set-Alias -Name 'syhg' -Value 'Sync-HgRepository' |