Case-insensitive diffs in Mercurial
Asked Answered
C

3

7

I'm using Mercurial (specifically TortoiseHg on Windows) to do version control of VBA code. Anybody who's tried this knows that VBA changes the case of every variable throughout a project whenever any declaration of that variable is changed anywhere in the project (regardless of scope). It makes version control a nightmare.

I would like to ignore case changes in my source code when performing diffs. What is the easiest way to do this? (some option for diff that I'm missing, an external diff utility, something else?)

NOTE: I am not talking about dealing with 'case-insensitive filenames' (yes, I'm talking to you Google...)

Carlie answered 5/3, 2010 at 22:38 Comment(2)
We meet again my friend! Did you ever find a solution, or perhaps a VCS that can do case insensitive diffs?Horizontal
@RubberDuck: You inspired me to finally post the approach I have been using for the past several years. I haven't solved the problem completely, but I've stripped out much of the pain. Basically, I ignore files where the only changes are to the casing. If the file has legitimate changes, I'm still stuck seeing the case changes as well, but it's (a lot) better than nothing.Carlie
T
7

You can do that when diffing for your on-screen consumption using the ExtDiff Extension.

  [extensions]
  hgext.extdiff =

  [extdiff]
  # add new command that runs GNU diff(1) in case-insensitive mode
  cmd.mydiff = diff
  opts.mydiff = -i

Then you'd run hg mydiff from the command line. That, of course, requires you have a diff binary installed be it gnu's or other.

However, that's not going to be as helpful as you might like because internally, of course, Mercurial can't ignore case -- it's taking the cryptographic hash of the file contents, and those don't allow for wiggle room. So if you get this set up you'll do hg mydiff, and see no changes, and then do hg commit and see changes all over the place.

So you can make this work on-screen, but not fundamentally.

One option would be to find a visual basic code-cleaner, similar to indent for C-like languages, that normalizes variable case and run that in a mercurial commit hook. Then at least all the code going into source control will be consistent and you can diff across revisions accurately.

Thenar answered 5/3, 2010 at 22:50 Comment(1)
Sounds like some sort of code-cleaner will be my only option. Thanks for your help.Carlie
S
2

If you are okay with having your code in all lower-case, say, then you could employ the encode/decode hooks for this. It would work like this:

[encode]
*.vba = tr A-Z a-z

This will encode the file content in lower-case whenever you do a commit. The diffs are also computed based on the encoded (repository) version of the files.

Consider a file that contains

hello

Changing it in your working copy to

Hello World

will give a diff of

% hg diff
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,1 +1,1 @@
-hello
+hello world

Notice how the capital "H" and "W" has been ignored.

I don't really know anything about VBA code, so I'm not 100% sure this solution works for you. But I hope it can be a starting point.

One drawback is that you'll need to set this encode rule for all your repositories. The reposettings extension can help you here.

Sarmatia answered 6/3, 2010 at 22:15 Comment(1)
That wouldn't really work for me in this case, but it's a great tip that I'll try to remember for the future. Thanks for the help.Carlie
C
2

Here's the solution I have settled on. It is far from ideal, but better than the other alternatives I've considered.

I created an Autohotkey script that does the following:

  • reverts MS Access files in a repository with detected changes (to .orig files)
  • reads in the .orig file (the one with the changes)
  • reads in the existing file (the one already in the repository)
  • converts the text of both files to lower case
  • compares the lower case contents of the files
  • if the files still differ, the .orig file is restored so it may be committed to the repository
  • if the files are the same (i.e., they differ only in case, the .orig file is deleted because we don't care about those changes)

For files that have actual changes that we care about, I still see the case changes that were made as well. If that results in a lot of noise, I open the file in a comparison tool that allows case-insensitive compares (e.g., kdiff).

It's not a perfect solution, but it removes about 90% of the frustration for me.

Here's my script. Note that the script includes another Autohotkey script, ConsoleApp.ahk, which provides a function named, ConsoleApp_RunWait(). This is a 3rd party script that no longer works very well with 64-bit AHK, so I'm not including it as part of my answer. Any AHK function that executes a command line and returns the output as a string will suffice.

; This script checks an MS Access source directory and reverts all files whose only modifications are to the 
; case of the characters within the file.

#Include %A_ScriptDir%\ConsoleApp.ahk
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

; Allow for custom path to hg (support for moving to TortoiseHg 2.0)
IniRead hg, %A_ScriptDir%\LocalSettings\Settings.cfg, TortoiseHg, hg_path, hg

if 0 < 1  ; The left side of a non-expression if-statement is always the name of a variable.
{
    MsgBox Usage:`n`HgIgnoreCase DirectoryWithFilesToScrub
    ExitApp
}

SrcDir = %1%
StringReplace SrcDir, SrcDir, ", , All

StringRight test, SrcDir, 1 ; add trailing slash if necessary
ifnotequal test, \
    SrcDir = %SrcDir%\

RestoreOriginals(SrcDir)
RevertCaseChangeModifiedFiles(SrcDir)

RevertCaseChangeModifiedFiles(SrcDir) {
global hg
    includes =  -I "*.form" -I "*.bas" -I "*.report" -I "*.table"
    cmdline = %hg% revert --all %includes%

    ;Don't revert items that have been removed completely
    Loop 3
    {
        Result := ConsoleApp_RunWait(hg . " status -nrd " . includes, SrcDir)
        If (Result)
            Break
    }
    Loop parse, Result, `n, `r
    {
        if (A_LoopField)
            cmdline = %cmdline% -X "%A_LoopField%"
    }
    Result =
    ;msgbox %cmdline%
    ;revert all modified forms, reports, and code modules
    Loop 3
    {

        Result := ConsoleApp_RunWait(cmdline, SrcDir)
        If (Result)
            Break
    }
    ;MsgBox %Result%

    Loop parse, Result, `n, `r
    {
        StringLeft FileStatus, A_LoopField, 9
        If (FileStatus = "reverting")
        {
            StringMid FName, A_LoopField, 11
            FullPath = %SrcDir%%FName%
            ToolTip Checking %FullPath%
            RestoreIfNotEqual(FullPath, FullPath . ".orig")
        }
    }
    ToolTip
}

RestoreIfNotEqual(FName, FNameOrig) {
    FileRead File1, %FName%
    FileRead File2, %FNameOrig%

    StringLower File1, File1
    StringLower File2, File2
    ;MsgBox %FName%`n%FNameOrig%
    If (File1 = File2)
        FileDelete %FNameOrig%
    Else
        FileMove %FNameOrig%, %FName%, 1
}

RestoreOriginals(SrcDir) {
    Loop %SrcDir%*.orig
    {
        ;MsgBox %A_LoopFileLongPath%`n%NewName%
        NewName := SubStr(A_LoopFileLongPath, 1, -5)
        FileMove %A_LoopFileLongPath%, %NewName%, 1
    }
    while FileExist(SrcDir . "*.orig")
        Sleep 10
}
Carlie answered 13/10, 2015 at 14:57 Comment(1)
Thanks for confirming that this is a useful approach. I was thinking of something similar.Horizontal

© 2022 - 2024 — McMap. All rights reserved.