Functions/Split-HgXml.ps1
function Split-HgXml { <# .SYNOPSIS Converts Mercurial's XML style output to PowerShell objects. .DESCRIPTION Mercurial's log-like commands all output to standard out. This doesn't work well with PowerShell's pipeline. This command takes the output from Mercurial's xml style and converts that output into PowerShell objects and sends those objects down the pipeline. For best results, use the xml style with the --debug parameter. The gets all information about a changeset instead of Mercurial's limited, default set. The objects returned represent individual changsets and have the following properties: Revision: The numeric identifier. Node: The node identifier. Parent1: The first parent, with properties Revision and Node. Null if the changeset had no parents. Parent2: The second parent, with properties Revision and Node. Null if the changeset wasn't a merge. Date: The date. Description: The commit message. Author: The user who made the change, which is an object with Name and Email properties. Branch: The branch. Tags: An array of the changeset's tags. Files: An array of all the files that were part of the changeset. FileAdds: An array of all files added by the changeset. FileDeletes: An array of all the files deleted by the changeset. FileModifications: An array of all files modified by the changeset. Copies: An array of objects representing renames in the changeset. Each object has a Source and Destination property. ALIASES slhgx .EXAMPLE hg log --style xml --debug | Split-HgXml Returns generic objects representing all the changesets in the current repository, e.g. Author : PsHg Test User <pshg@example.com> Revision : 2 Node : 91069564c902f97622a94305d9b5ba969590148a Parent1 : @{Revision=1; Node=b15b8cc69a8bc9c165d48509554f4d616b746a75} Parent2 : Date : 4/26/2012 8:23:32 AM Copies : {a.txt -> a2.txt}, {b.txt -> b2.txt}} Files : {a2.txt, b2.txt, a.txt, b.txt} FileAdds : {a2.txt, b2.txt} FileDeletes : {a.txt, b.txt} FileModifications : {} Description : Renaming files. Branch : default Tags : {tip} .EXAMPLE > $xml = ((hg log --style xml --debug) -join "") > Split-HgXml -InputObject $xml Converts a string of Mercurial's XML style output to objects. #> [CmdletBinding()] [OutputType([PsHg.ChangesetInfo])] param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [AllowEmptyString()] [string] # The XML to parse. Can come from the pipeline or an explicit string. $InputObject ) begin { Set-StrictMode -Version 'Latest' $entry = $null $xml = New-Object Text.StringBuilder $xmlOutputStarted = $false $xmlReady = $false } process { if( $pscmdlet.MyInvocation.ExpectingInput ) { if( $_ -eq '<log>' ) { $xmlOutputStarted = $true return } elseif( $_ -eq '</log>' ) { $xmlOutputStarted = $false return } if( -not $xmlOutputStarted ) { return } if( $_ -like '<logentry *' ) { $xml = New-Object Text.StringBuilder "<?xml version=""1.0""?>`n<log>`n" } [void] $xml.Append($_) if( $_ -like '</logentry>' ) { [void] $xml.Append('</log>') $xmlReady = $true } } else { $xml = New-Object Text.StringBuilder $InputObject $xmlReady = $true } if( -not $xmlReady ) { return } $xmlDoc = [xml] ($Xml.ToString()) filter Get-Parent { param( $ParentXml ) if( -not $parentXml -or $parentXml.revision -eq -1 ) { return $null } New-Object 'PsHg.ChangesetID' $parentXml.revision,$parentXml.node } $xmlDoc.log.logentry | ForEach-Object { $id = New-Object 'PsHg.ChangesetID' $_.revision,$_.node $parent1 = $null $parent2 = $null if( $_ | Get-Member 'parent' ) { if( $_.parent -is 'Array' ) { $parent1 = Get-Parent $_.parent[0] $parent2 = Get-Parent $_.parent[1] } else { $parent1 = Get-Parent $_.parent } } $date = $_.date $description = $_.msg.InnerText $author = New-Object 'PsHg.AuthorInfo' $_.author.InnerText,$_.author.email $branch = 'default' if( $_ | Get-Member 'branch' ) { $branch = $_.branch } $phase = $null if( $_ | Get-Member 'phase' ) { $phase = New-Object 'PsHg.PhaseInfo' $_.phase.InnerText,$_.phase.index } $diffstats = $null if( ($_ | Get-Member 'diffstat') -and ($_.diffstat -match '^(\d+): \+(\d+)/-(\d+)$') ) { $diffstats = New-Object 'PsHg.DiffInfo' $Matches[1],$Matches[2],$Matches[3] } $entry = New-Object 'PsHg.ChangesetInfo' $id,$branch,$parent1,$parent2,$author,$date,$description,$phase,$diffstats if( $_ | Get-Member 'copies' ) { $_.copies.copy | Where-Object { $_ } | ForEach-Object { $copy = New-Object 'PsHg.CopyInfo' $_.source,$_.InnerText [void]$entry.Copies.Add( $copy ) } } $files = @{ } $usingPshgStyle = $false if( ($_ | Get-Member 'files') -and ($_.files | Get-Member 'file') ) { $usingPshgStyle = $true foreach( $file in $_.files.file ) { $files[$file] = $file [void]$entry.Files.Add( $file ) } } if( ($_ | Get-Member 'paths') -and ($_.paths | Get-Member 'path') ) { $_.paths.path | Where-Object { -not $usingPshgStyle -or $files.ContainsKey( $_.InnerText ) } | ForEach-Object { $path = $_.InnerText if( -not $usingPshgStyle ) { [void]$entry.Files.Add( $path ) } switch( $_.action ) { 'M' { [void]$entry.FileModifications.Add( $path ) } 'A' { [void]$entry.FileAdds.Add( $path ) } 'R' { [void]$entry.FileDeletes.Add( $path ) } } } } if( $_ | Get-Member 'tag' ) { $_.tag | ForEach-Object { [void]$entry.Tags.Add( $_ ) } } if( $_ | Get-Member 'extra' ) { $_.extra | ForEach-Object { [void]$entry.Extras.Add( $_.key, $_.InnerText ) } } if( $_ | Get-Member 'bookmark' ) { $_.bookmark | ForEach-Object { [void]$entry.Bookmarks.Add( $_ ) } } $entry } $entry = $null $xml = $null $xmlReady = $false } end { if( $entry ) { $entry } } } Set-Alias -Name 'slhgx' -Value 'Split-HgXml' |