Functions/Rename-HgItem.ps1


function Rename-HgItem
{
    <#
    .SYNOPSIS
    Renames a file/directory in Mercurial.
     
    .DESCRIPTION
    This does not move the item. It changes its name only.
     
    Properly handles changing the case of that item. Mercurial is case-sensitive. Windows is not. There may be times when you need to change just the case of a file or directory. This function takes the path to a file or directory, and a new name for the item, and renames the items so it has the expected case.
     
    This is a two-step operation, so requires two commits. Only the renamed files/directories are committed. This means if you're renaming a directory, and you have uncommitted changes under that directory, they may get committed. It's always best to have a clean repository.
     
    ALIASES
      rnhgi, hgrename
       
    .EXAMPLE
    Rename-HgItem -Path .\Test\Test-ConvertHgItemCase.ps1 -NewName Test-RenameHgItem.ps1
     
    Renames the file at .\Test\Test-ConvertHgItemCase.ps1 to Test-RenameHgItem.ps1.
     
    .EXAMPLE
    Rename-HgItem -Path WebSite -NewName Website
     
    Renames the WebSite directory to Website.
     
    .EXAMPLE
    Rename-HgItem -Path src\Code.cs -NewName code.cs
     
    Renames the src\Code.cs file to src\code.cs. Note that NewName does NOT have a directory component.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        # The path to the item whose case should change.
        $Path,
        
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        # The item's new name. Must not have a directory component. Should just be the new name.
        $NewName
    )
    
    if( (Split-Path -Parent $NewName) -ne '' )
    {
        Write-Error "'$NewName' should be a filename, not a path."
        return
    }
    
    $currentName = Split-Path -Leaf $Path
    
    if( $currentName -ceq $NewName )
    {
        Write-Error "'$Path' is already named '$NewName'."
        return
    }
    
    $isDir = (Test-Path $Path -PathType Container)
    $isFile = (Test-Path $Path -PathType Leaf)
    
    if( -not $isDir -and -not $isFile )
    {
        Write-Error "'$Path' not found."
        return
    }
    
    $repoRoot = Resolve-HgRoot -Path $Path
    if( -not $repoRoot )
    {
        return
    }
        
    # Really re-naming something. Versions of Mercurial greater than 2.1.1 added support for case-only renames
    if( $currentName -ne $NewName -or ((Get-HgVersion) -gt ([Version]'2.1.1')) )
    {
        $oldPath = Resolve-HgPath -Path $Path
        $newPathParent = Split-Path -Parent $oldPath
        if( $newPathParent )
        {
            $newPath = Join-Path $newPathParent $NewName
        }
        else
        {
            $newPath = $NewName
        }
        
        Push-Location $repoRoot
        try
        {
            if( $isDir )
            {
                $destinationDir = Join-Path $repoRoot $newPath
                if( -not (Test-Path $destinationDir) )
                {
                    New-Item $destinationDir -ItemType Directory
                }
                
                if( $currentName -eq $NewName -and $currentName -cne $NewName )
                {
                    $tempSuffix = [IO.Path]::GetRandomFileName()
                    $tempName = '{0}.{1}' -f $newPath,$tempSuffix
                    
                    hg rename $oldPath $tempName
                    if( (Test-Path -Path $oldPath -PathType Container) )
                    {
                        Get-ChildItem $oldPath * |
                            Move-Item -Destination $tempName
                        Remove-Item $oldPath
                    }
                    
                    hg rename $tempName $newPath
                    if( (Test-Path -Path $tempName -PathType Container) )
                    {
                        Get-ChildItem $tempName * |
                            Move-Item -Destination $newPath
                        Remove-Item $tempName
                    }
                }
                else
                {
                    hg rename "$oldPath\*" $newPath
                }
            }
            else
            {
                hg rename $oldPath $newPath
            }
            Write-Host "Renamed $oldPath -> $newPath. Don't forget to commit."
            return
        }
        finally
        {
            Pop-Location
        }
    }
    
    # Changing the case.
    if( $isDir )
    {
        $dirRoot = Split-Path -Parent $Path
        $pathForCommit = $Path
        $Path = "$Path\*"
        $tempPath = "$NewName.casefix"
        $newPath = $NewName
        if( $dirRoot )
        {
            $tempPath = Join-Path $dirRoot $tempPath
            $newPath = Join-Path $dirRoot $NewPath
        }
        
        if( -not (Test-Path $tempPath -PathType Container) )
        {
            New-Item $tempPath -ItemType Directory
        }
    }
    elseif( $isFile )
    {
        $fileRoot = Split-Path -Parent $Path
        $tempPath = "$newName.casefix"
        $newPath = $NewName
        if( $fileRoot )
        {
            $tempPath = Join-Path $fileRoot "$newName.casefix"
            $newPath = Join-Path $fileRoot $newName
        }
        $pathForCommit = $Path
    }
            
    Write-Host "Renaming $Path -> $tempPath"
    hg rename $Path $tempPath
    if( $LastExitCode )
    {
        return
    }
    
    hg commit -m "Renaming $pathForCommit -> $tempPath." $pathForCommit $tempPath
    if( $LastExitCode )
    {
        return
    }
    
    Write-Host "Renaming $tempPath -> $newPath"
    hg rename $tempPath $newPath
    if( $LastExitCode )
    {
        return
    }
    
    hg commit -m "Renaming $tempPath -> $newPath." $tempPath $newPath
    if( $LastExitCode )
    {
        return 
    }
}

Set-Alias -Name 'hgrename' -Value 'Rename-HgItem'
Set-Alias -Name 'rnhgi' -Value 'Rename-HgItem'