PSSvnBranch.psm1

class BranchStatus {
    [string]    $Url
    [string]    $BranchName
    [bool]      $Merged
    [int[]]     $Revisions
}

class BranchDetails {
    [string]                $Url
    [string]                $BranchName
    [bool]                  $Merged
    [PoshSvn.SvnLogOutput]  $Created
    [int[]]                 $Revisions
}

class BranchAction {
    [string]    $Url
    [string]    $BranchName
    [int]       $MergeRevision
    [string]    $MergeLogMessage
    [bool]      $FullMerge
    [int[]]     $UnmergedRevisions
}

function Get-SvnBranchSummary {
    [CmdletBinding()]
    [OutputType([BranchStatus])]
    param (
        [string][Parameter(Mandatory = $true)]
        $RootUrl,

        [string][Parameter(Mandatory = $true)]
        $BranchName
    )

    process {
        $url = "$RootUrl/branches/$BranchName"
        $trunk = "$RootUrl/trunk"

        $eligible_revs = svn-mergeinfo -Source $url -Target $trunk -ShowRevisions Eligible -ProgressAction SilentlyContinue

        if ($null -eq $eligible_revs) {
            $obj = [BranchStatus]@{
                Url        = $url
                BranchName = $BranchName
                Merged     = $true
            }
            Write-Output $obj
        }
        else {
            $obj = [BranchStatus]@{
                Url        = $url
                BranchName = $BranchName
                Merged     = $false
                Revisions  = ($eligible_revs | ForEach-Object { $_.Revision })
            }
            Write-Output $obj
        }
    }
}

function Get-SvnProjectStatus {
    [CmdletBinding()]
    [OutputType([BranchStatus])]
    param (
        [string][Parameter(Mandatory = $true)]
        $RootUrl,

        [string][Parameter(Mandatory = $false)]
        $Trunk = "trunk"
    )

    process {
        $branchesUrl = "$RootUrl/branches"

        Write-Progress -activity "Collecting branches..." -status $branchesUrl -PercentComplete 0

        $branches = svn-list $branchesUrl -ProgressAction SilentlyContinue
        $i = 0

        $branches | ForEach-Object {
            $url = $_.Uri.ToString()
            $i++

            $obj = Get-SvnBranchSummary -RootUrl $RootUrl -BranchName $_.Name
            Write-Output $obj

            Write-Progress -activity "Retrieving branch information..." -status $url -PercentComplete ($i * 100 / $branches.Count)
        }
    }
}

function Get-SvnBranchDetails {
    [CmdletBinding()]
    [OutputType([BranchDetails])]
    param (
        [string][Parameter(Mandatory = $true)]
        $RootUrl,

        [string][Parameter(Mandatory = $true)]
        $BranchName
    )

    process {
        $url = "$RootUrl/branches/$BranchName"
        $trunk = "$RootUrl/trunk"

        $mergeinfo = svn-mergeinfo -Source $url -Target $trunk -ProgressAction SilentlyContinue
        $created = svn-log -Target $mergeinfo.BaseUrl.ToString() -Revision $mergeinfo.BaseRevision -ProgressAction SilentlyContinue
        $summary = Get-SvnBranchSummary -RootUrl $RootUrl -BranchName $BranchName

        $obj = [BranchDetails]@{
            Url        = $url
            BranchName = $BranchName
            Merged     = $summary.Merged
            Created    = $created
            Revisions  = $summary.Revisions
        }
        Write-Output $obj
    }
}

function Search-SvnBranchHistory {
    [CmdletBinding()]
    [OutputType([BranchAction])]
    param (
        [string][Parameter(Mandatory = $true)]
        $RootUrl,
        [int][Parameter(Mandatory = $false)]
        $Limit = -1,
        [PoshSvn.SvnRevision][Parameter(Mandatory = $false)]
        $StartRevision = "HEAD"
    )

    process {
        $trunk = "$RootUrl/trunk"
        $branchesUrl = "$RootUrl/branches"

        $branches = svn-list $branchesUrl -ProgressAction SilentlyContinue
        $rev = New-Object PoshSvn.SvnRevisionRange $StartRevision, 0
        $log = svn-log $trunk -Revision $rev -Limit $Limit -ProgressAction SilentlyContinue

        $i = 0
        $log | ForEach-Object {
            Write-Progress -activity "Scanning history" -status "Processing revision: " -PercentComplete ($i * 100 / $log.Count)
            $diff = svn-diff $trunk -Depth Empty -Change $_.Revision -ProgressAction SilentlyContinue
            $rev = $_.Revision
            $message = $_.Message

            if ($diff) {
                $branches | ForEach-Object {
                    $branchName = $_.Name

                    if ($diff | Select-String -SimpleMatch "Merged $($_.BasePath)/${branchName}:") {
                        $summary = Get-SvnBranchSummary -RootUrl $RootUrl -BranchName $branchName

                        $obj = [BranchAction]@{
                            Url               = "$branchesUrl/$branchName"
                            BranchName        = $branchName
                            MergeRevision     = $rev
                            MergeLogMessage   = $message
                            FullMerge         = $summary.Merged
                            UnmergedRevisions = $summary.Revisions
                        }
                        Write-Output $obj
                    }
                }
            }
        }
    }
}

function Remove-SvnBranch {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(ValueFromPipeline, Mandatory)]
        [psobject]$InputObject,
        [Parameter()]
        [switch]$Force
    )

    process {
        if ($InputObject.Url) {
            $url = $InputObject.Url
        }
        else {
            $url = $InputObject.ToString()
        }

        if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
            $ConfirmPreference = 'None'
        }

        $branchName = [System.IO.Path]::GetFileName($url)
        $message = "Remove branch '$branchName'."

        if ($PSCmdlet.ShouldProcess($url, $message)) {
            svn-remove $url -Message $message
        }
    }
}