Thursday, November 1, 2007

PowerShell and Subversion

I try to use PowerShell for everything I can. Because we use Subversion for source control at the shop where I work, I have written a few PowerShell functions to make life a little easier when using Subversion at the command line.

Some might argue that most of this stuff can be done for you by TortoiseSVN or something similar. That may be true, but where's the fun in that? I was using TortoiseSVN when I first started playing around with PowerShell, but I found that using it forced me to keep a Windows Explorer window open a lot, which kept me away from the command line. I wanted to force myself to use PowerShell for as much as possible, so I uninstalled TortoiseSVN and I've never looked back.

The first function is called Get-SvnStatus. It uses the Subversion "status" command with the "--xml" switch and displays the status of versioned files and directories little more nicely. More importantly, the Status and Path are properties are on the objects output by this function. This means they can be used farther down the pipeline.

function Get-SvnStatus( [string] $filter = "^(?!unversioned)", [switch] $NoFormat )
{
    # powershell chokes on "wc-status" and doesn't like two definitions of "item"
    [xml]$status = ( ( svn status --xml ) -replace "wc-status", "svnstatus" ) `
        -replace "item=", "itemstatus="
    
    $statusObjects = $status.status.target.entry | Where-Object { 
        $_.svnstatus.itemstatus -match $filter 
    } | Foreach-Object {
        $_ | Select-Object @{ Name = "Status"; Expression = { $_.svnstatus.itemstatus } }, 
                           @{ Name = "Path";   Expression = { Resolve-Path $_.path } }
    } | Sort-Object Status
    
    if ( $NoFormat )
    {
        $statusObjects
    }
    else
    {
        $statusObjects | Format-Table -Auto
    }
}


D:\Subversion\projects

PSH$ Get-SvnStatus

Status   Path
------   ----
modified D:\Subversion\projects\KickButtApp\KickButtApp.cpp
modified D:\Subversion\projects\KickButtApp\KickButtApp.h


The filter can be any regular expression to match against the status of the item. The default filter doesn't allow unversioned files through. The "NoFormat" switch is there in case the Status or Path properties of the objects created need to be used down the pipeline.

The next function is Compare-SvnRevision. It uses Subversion's "cat" command to get a copy of a file at a specified revision to compare with your current working copy. The default value for the revision is "HEAD", which will get the latest version in the repository.

function Compare-SvnRevision( [string] $path, [string] $revision = "HEAD" )
{
    $url = Get-SvnUrl $path

    $fileInfo = New-Object System.IO.FileInfo $path

    svn cat -r $revision $url > "TEMP - $($fileInfo.Name)"

    WinMerge $path "TEMP - $($fileInfo.Name)"

    $winMerge = Get-Process WinMerge

    while ( $winMerge -eq $null )
    {
        $winMerge = Get-Process WinMerge
    }

    $winMerge.WaitForExit()

    Remove-Item "TEMP - $($fileInfo.Name)"
}


This function uses WinMerge to perform the comparison, which is my favorite two-way merge tool. It also assumes WinMerge is in $env:Path.

The next function, Resolve-SvnConflicts, uses Get-SvnStatus to get all the files in a "conflicted" state after an update, commit, or merge. It then uses DiffMerge to do a three-way merge of the base revision, your working copy, and the head revision. You are prompted to indicate if you were able to resolve conflicts, and if you have, the "resolved" command is performed on the file. This function assumes DiffMerge is in $env:Path.

function Resolve-SvnConflicts
{
    Get-SvnStatus "conflicted" -NoFormat | Foreach-Object { 
        $file = ( Resolve-Path $_.Path )

        Write-Output "Merging $( $file )..."

        $baseRevision, $headRevision = ( Get-ChildItem "$file.r*" | Sort-Object )

        DiffMerge /t1 "Base Revision" /t2 "Working Copy" /t3 "Head Revision" `
            $baseRevision, "$file.mine", $headRevision

        $diffMerge = Get-Process DiffMerge

        while ( $diffMerge -eq $null )
        {
            $diffMerge = Get-Process DiffMerge
        }

        $diffMerge.WaitForExit()

        Write-Output "Conflicts resolved? [yes, no]"

        $resolved = Read-Host

        if ( $resolved -imatch "^y(es)?$" )
        {
            Copy-Item "$file.mine" $file -Force
            svn resolved $file
        }
    }
}


These next two functions just use the Subversion "info" command with the "--xml" switch to get the URL or revision for a versioned file. They both have a switch parameter to indicate if you want the result to be put on the clipboard. To put these items on the clipboard, I use a Cmdlet I wrote myself, but the PowerShell Community Extensions have a Cmdlet with the same name that will do the same thing and, apparently, more.

function Get-SvnUrl( [string] $path = ".", [switch] $Clipboard )
{
    $url = ( [xml]( svn info --xml $path ) ).info.entry.url

    if ( $Clipboard )
    {
        Set-Clipboard $url  
    }

    $url
}

function Get-SvnRevision( [string] $path = ".", [switch] $Clipboard )
{
    $revision = ( [xml]( svn info --xml $path ) ).info.entry.revision

    if ( $Clipboard )
    {
        Set-Clipboard $revision  
    }

    $revision
}


Well, there you have it. I use these functions every day, so I hope sharing them will make someone else's life a little easier.

2 comments:

Karthik said...
This comment has been removed by the author.
Karthik said...

Hi, just came across your blog and I thought it was cool. I am working on putting together a PS script to check the health of SVN URL and write it onto a file. I haven't had much luck so far. It works fine when the site is available but doesn't report when it's not. I can share the code if you like but any alternate thoughts on this would be great. Thanks.