Index: /trunk/src/bldprogs/Makefile.kmk
===================================================================
--- /trunk/src/bldprogs/Makefile.kmk	(revision 40533)
+++ /trunk/src/bldprogs/Makefile.kmk	(revision 40534)
@@ -38,6 +38,8 @@
  scm_SOURCES = \
 	scm.cpp \
+	scmdiff.cpp \
+	scmrw.cpp \
 	scmstream.cpp \
-	scmdiff.cpp 
+	scmsubversion.cpp 
  scm_LIBS = \
  	$(LIB_RUNTIME)
Index: /trunk/src/bldprogs/scm.cpp
===================================================================
--- /trunk/src/bldprogs/scm.cpp	(revision 40533)
+++ /trunk/src/bldprogs/scm.cpp	(revision 40534)
@@ -35,5 +35,5 @@
 #include <iprt/string.h>
 
-#include "scmstream.h"
+#include "scm.h"
 #include "scmdiff.h"
 
@@ -49,109 +49,4 @@
 *   Structures and Typedefs                                                    *
 *******************************************************************************/
-/** Pointer to const massager settings. */
-typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
-
-/**
- * SVN property.
- */
-typedef struct SCMSVNPROP
-{
-    /** The property. */
-    char           *pszName;
-    /** The value.
-     * When used to record updates, this can be set to NULL to trigger the
-     * deletion of the property. */
-    char           *pszValue;
-} SCMSVNPROP;
-/** Pointer to a SVN property. */
-typedef SCMSVNPROP *PSCMSVNPROP;
-/** Pointer to a const  SVN property. */
-typedef SCMSVNPROP const *PCSCMSVNPROP;
-
-
-/**
- * Rewriter state.
- */
-typedef struct SCMRWSTATE
-{
-    /** The filename.  */
-    const char     *pszFilename;
-    /** Set after the printing the first verbose message about a file under
-     *  rewrite. */
-    bool            fFirst;
-    /** The number of SVN property changes. */
-    size_t          cSvnPropChanges;
-    /** Pointer to an array of SVN property changes. */
-    PSCMSVNPROP     paSvnPropChanges;
-} SCMRWSTATE;
-/** Pointer to the rewriter state. */
-typedef SCMRWSTATE *PSCMRWSTATE;
-
-/**
- * A rewriter.
- *
- * This works like a stream editor, reading @a pIn, modifying it and writing it
- * to @a pOut.
- *
- * @returns true if any changes were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-
-
-/**
- * Configuration entry.
- */
-typedef struct SCMCFGENTRY
-{
-    /** Number of rewriters. */
-    size_t          cRewriters;
-    /** Pointer to an array of rewriters. */
-    PFNSCMREWRITER const  *papfnRewriter;
-    /** File pattern (simple).  */
-    const char     *pszFilePattern;
-} SCMCFGENTRY;
-typedef SCMCFGENTRY *PSCMCFGENTRY;
-typedef SCMCFGENTRY const *PCSCMCFGENTRY;
-
-
-/**
- * Source Code Massager Settings.
- */
-typedef struct SCMSETTINGSBASE
-{
-    bool            fConvertEol;
-    bool            fConvertTabs;
-    bool            fForceFinalEol;
-    bool            fForceTrailingLine;
-    bool            fStripTrailingBlanks;
-    bool            fStripTrailingLines;
-    /** Only process files that are part of a SVN working copy. */
-    bool            fOnlySvnFiles;
-    /** Only recurse into directories containing an .svn dir.  */
-    bool            fOnlySvnDirs;
-    /** Set svn:eol-style if missing or incorrect. */
-    bool            fSetSvnEol;
-    /** Set svn:executable according to type (unusually this means deleting it). */
-    bool            fSetSvnExecutable;
-    /** Set svn:keyword if completely or partially missing. */
-    bool            fSetSvnKeywords;
-    /**  */
-    unsigned        cchTab;
-    /** Only consider files matching these patterns.  This is only applied to the
-     *  base names. */
-    char           *pszFilterFiles;
-    /** Filter out files matching the following patterns.  This is applied to base
-     *  names as well as the absolute paths.  */
-    char           *pszFilterOutFiles;
-    /** Filter out directories matching the following patterns.  This is applied
-     *  to base names as well as the absolute paths.  All absolute paths ends with a
-     *  slash and dot ("/.").  */
-    char           *pszFilterOutDirs;
-} SCMSETTINGSBASE;
-/** Pointer to massager settings. */
-typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
 
 /**
@@ -207,74 +102,10 @@
 
 
-/**
- * File/dir pattern + options.
- */
-typedef struct SCMPATRNOPTPAIR
-{
-    char *pszPattern;
-    char *pszOptions;
-} SCMPATRNOPTPAIR;
-/** Pointer to a pattern + option pair. */
-typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
-
-
-/** Pointer to a settings set. */
-typedef struct SCMSETTINGS *PSCMSETTINGS;
-/**
- * Settings set.
- *
- * This structure is constructed from the command line arguments or any
- * .scm-settings file found in a directory we recurse into.  When recursing in
- * and out of a directory, we push and pop a settings set for it.
- *
- * The .scm-settings file has two kinds of setttings, first there are the
- * unqualified base settings and then there are the settings which applies to a
- * set of files or directories.  The former are lines with command line options.
- * For the latter, the options are preceded by a string pattern and a colon.
- * The pattern specifies which files (and/or directories) the options applies
- * to.
- *
- * We parse the base options into the Base member and put the others into the
- * paPairs array.
- */
-typedef struct SCMSETTINGS
-{
-    /** Pointer to the setting file below us in the stack. */
-    PSCMSETTINGS        pDown;
-    /** Pointer to the setting file above us in the stack. */
-    PSCMSETTINGS        pUp;
-    /** File/dir patterns and their options. */
-    PSCMPATRNOPTPAIR    paPairs;
-    /** The number of entires in paPairs. */
-    uint32_t            cPairs;
-    /** The base settings that was read out of the file. */
-    SCMSETTINGSBASE     Base;
-} SCMSETTINGS;
-/** Pointer to a const settings set. */
-typedef SCMSETTINGS const *PCSCMSETTINGS;
-
-
-/*******************************************************************************
-*   Internal Functions                                                         *
-*******************************************************************************/
-static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
-
-
 /*******************************************************************************
 *   Global Variables                                                           *
 *******************************************************************************/
+const char          g_szTabSpaces[16+1]     = "                ";
 static const char   g_szProgName[]          = "scm";
 static const char  *g_pszChangedSuff        = "";
-static const char   g_szTabSpaces[16+1]     = "                ";
 static bool         g_fDryRun               = true;
 static bool         g_fDiffSpecialChars     = true;
@@ -1119,5 +950,5 @@
  * @param   ...                 Format arguments.
  */
-static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
+void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
 {
     if (iLevel <= g_iVerbosity)
@@ -1143,900 +974,6 @@
 
 
-/* -=-=-=-=-=- subversion -=-=-=-=-=- */
-
-#define SCM_WITHOUT_LIBSVN
-
-#ifdef SCM_WITHOUT_LIBSVN
-
-/**
- * Callback that is call for each path to search.
- */
-static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
-{
-    char   *pszDst = (char *)pvUser1;
-    size_t  cchDst = (size_t)pvUser2;
-    if (cchDst > cchPath)
-    {
-        memcpy(pszDst, pchPath, cchPath);
-        pszDst[cchPath] = '\0';
-#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
-        int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
-#else
-        int rc = RTPathAppend(pszDst, cchDst, "svn");
-#endif
-        if (   RT_SUCCESS(rc)
-            && RTFileExists(pszDst))
-            return VINF_SUCCESS;
-    }
-    return VERR_TRY_AGAIN;
-}
-
-
-/**
- * Finds the svn binary.
- *
- * @param   pszPath             Where to store it.  Worst case, we'll return
- *                              "svn" here.
- * @param   cchPath             The size of the buffer pointed to by @a pszPath.
- */
-static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
-{
-    /** @todo code page fun... */
-    Assert(cchPath >= sizeof("svn"));
-#ifdef RT_OS_WINDOWS
-    const char *pszEnvVar = RTEnvGet("Path");
-#else
-    const char *pszEnvVar = RTEnvGet("PATH");
-#endif
-    if (pszPath)
-    {
-#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
-        int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
-#else
-        int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
-#endif
-        if (RT_SUCCESS(rc))
-            return;
-    }
-    strcpy(pszPath, "svn");
-}
-
-
-/**
- * Construct a dot svn filename for the file being rewritten.
- *
- * @returns IPRT status code.
- * @param   pState              The rewrite state (for the name).
- * @param   pszDir              The directory, including ".svn/".
- * @param   pszSuff             The filename suffix.
- * @param   pszDst              The output buffer.  RTPATH_MAX in size.
- */
-static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
-{
-    strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
-    RTPathStripFilename(pszDst);
-
-    int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
-    if (RT_SUCCESS(rc))
-    {
-        rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
-        if (RT_SUCCESS(rc))
-        {
-            size_t cchDst  = strlen(pszDst);
-            size_t cchSuff = strlen(pszSuff);
-            if (cchDst + cchSuff < RTPATH_MAX)
-            {
-                memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
-                return VINF_SUCCESS;
-            }
-            else
-                rc = VERR_BUFFER_OVERFLOW;
-        }
-    }
-    return rc;
-}
-
-/**
- * Interprets the specified string as decimal numbers.
- *
- * @returns true if parsed successfully, false if not.
- * @param   pch                 The string (not terminated).
- * @param   cch                 The string length.
- * @param   pu                  Where to return the value.
- */
-static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
-{
-    size_t u = 0;
-    while (cch-- > 0)
-    {
-        char ch = *pch++;
-        if (ch < '0' || ch > '9')
-            return false;
-        u *= 10;
-        u += ch - '0';
-    }
-    *pu = u;
-    return true;
-}
-
-#endif /* SCM_WITHOUT_LIBSVN */
-
-/**
- * Checks if the file we're operating on is part of a SVN working copy.
- *
- * @returns true if it is, false if it isn't or we cannot tell.
- * @param   pState              The rewrite state to work on.
- */
-static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
-{
-#ifdef SCM_WITHOUT_LIBSVN
-    /*
-     * Hack: check if the .svn/text-base/<file>.svn-base file exists.
-     */
-    char szPath[RTPATH_MAX];
-    int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
-    if (RT_SUCCESS(rc))
-        return RTFileExists(szPath);
-
-#else
-    NOREF(pState);
-#endif
-    return false;
-}
-
-/**
- * Queries the value of an SVN property.
- *
- * This will automatically adjust for scheduled changes.
- *
- * @returns IPRT status code.
- * @retval  VERR_INVALID_STATE if not a SVN WC file.
- * @retval  VERR_NOT_FOUND if the property wasn't found.
- * @param   pState              The rewrite state to work on.
- * @param   pszName             The property name.
- * @param   ppszValue           Where to return the property value.  Free this
- *                              using RTStrFree.  Optional.
- */
-static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
-{
-    /*
-     * Look it up in the scheduled changes.
-     */
-    uint32_t i = pState->cSvnPropChanges;
-    while (i-- > 0)
-        if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
-        {
-            const char *pszValue = pState->paSvnPropChanges[i].pszValue;
-            if (!pszValue)
-                return VERR_NOT_FOUND;
-            if (ppszValue)
-                return RTStrDupEx(ppszValue, pszValue);
-            return VINF_SUCCESS;
-        }
-
-#ifdef SCM_WITHOUT_LIBSVN
-    /*
-     * Hack: Read the .svn/props/<file>.svn-work file exists.
-     */
-    char szPath[RTPATH_MAX];
-    int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
-    if (RT_SUCCESS(rc) && !RTFileExists(szPath))
-        rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
-    if (RT_SUCCESS(rc))
-    {
-        SCMSTREAM Stream;
-        rc = ScmStreamInitForReading(&Stream, szPath);
-        if (RT_SUCCESS(rc))
-        {
-            /*
-             * The current format is K len\n<name>\nV len\n<value>\n" ... END.
-             */
-            rc = VERR_NOT_FOUND;
-            size_t const    cchName = strlen(pszName);
-            SCMEOL          enmEol;
-            size_t          cchLine;
-            const char     *pchLine;
-            while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
-            {
-                /*
-                 * Parse the 'K num' / 'END' line.
-                 */
-                if (   cchLine == 3
-                    && !memcmp(pchLine, "END", 3))
-                    break;
-                size_t cchKey;
-                if (   cchLine < 3
-                    || pchLine[0] != 'K'
-                    || pchLine[1] != ' '
-                    || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
-                    || cchKey == 0
-                    || cchKey > 4096)
-                {
-                    RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
-                    rc = VERR_PARSE_ERROR;
-                    break;
-                }
-
-                /*
-                 * Match the key and skip to the value line.  Don't bother with
-                 * names containing EOL markers.
-                 */
-                size_t const offKey = ScmStreamTell(&Stream);
-                bool fMatch = cchName == cchKey;
-                if (fMatch)
-                {
-                    pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
-                    if (!pchLine)
-                        break;
-                    fMatch = cchLine == cchName
-                          && !memcmp(pchLine, pszName, cchName);
-                }
-
-                if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
-                    break;
-                if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
-                    break;
-
-                /*
-                 * Read and Parse the 'V num' line.
-                 */
-                pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
-                if (!pchLine)
-                    break;
-                size_t cchValue;
-                if (   cchLine < 3
-                    || pchLine[0] != 'V'
-                    || pchLine[1] != ' '
-                    || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
-                    || cchValue > _1M)
-                {
-                    RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
-                    rc = VERR_PARSE_ERROR;
-                    break;
-                }
-
-                /*
-                 * If we have a match, allocate a return buffer and read the
-                 * value into it.  Otherwise skip this value and continue
-                 * searching.
-                 */
-                if (fMatch)
-                {
-                    if (!ppszValue)
-                        rc = VINF_SUCCESS;
-                    else
-                    {
-                        char *pszValue;
-                        rc = RTStrAllocEx(&pszValue, cchValue + 1);
-                        if (RT_SUCCESS(rc))
-                        {
-                            rc = ScmStreamRead(&Stream, pszValue, cchValue);
-                            if (RT_SUCCESS(rc))
-                                *ppszValue = pszValue;
-                            else
-                                RTStrFree(pszValue);
-                        }
-                    }
-                    break;
-                }
-
-                if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
-                    break;
-                if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
-                    break;
-            }
-
-            if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
-            {
-                rc = ScmStreamGetStatus(&Stream);
-                RTMsgError("%s: stream error %Rrc\n", szPath, rc);
-            }
-            ScmStreamDelete(&Stream);
-        }
-    }
-
-    if (rc == VERR_FILE_NOT_FOUND)
-        rc = VERR_NOT_FOUND;
-    return rc;
-
-#else
-    NOREF(pState);
-#endif
-    return VERR_NOT_FOUND;
-}
-
-
-/**
- * Schedules the setting of a property.
- *
- * @returns IPRT status code.
- * @retval  VERR_INVALID_STATE if not a SVN WC file.
- * @param   pState              The rewrite state to work on.
- * @param   pszName             The name of the property to set.
- * @param   pszValue            The value.  NULL means deleting it.
- */
-static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
-{
-    /*
-     * Update any existing entry first.
-     */
-    size_t i = pState->cSvnPropChanges;
-    while (i-- > 0)
-        if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
-        {
-            if (!pszValue)
-            {
-                RTStrFree(pState->paSvnPropChanges[i].pszValue);
-                pState->paSvnPropChanges[i].pszValue = NULL;
-            }
-            else
-            {
-                char *pszCopy;
-                int rc = RTStrDupEx(&pszCopy, pszValue);
-                if (RT_FAILURE(rc))
-                    return rc;
-                pState->paSvnPropChanges[i].pszValue = pszCopy;
-            }
-            return VINF_SUCCESS;
-        }
-
-    /*
-     * Insert a new entry.
-     */
-    i = pState->cSvnPropChanges;
-    if ((i % 32) == 0)
-    {
-        void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
-        if (!pvNew)
-            return VERR_NO_MEMORY;
-        pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
-    }
-
-    pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
-    pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
-    if (   pState->paSvnPropChanges[i].pszName
-        && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
-        pState->cSvnPropChanges = i + 1;
-    else
-    {
-        RTStrFree(pState->paSvnPropChanges[i].pszName);
-        pState->paSvnPropChanges[i].pszName = NULL;
-        RTStrFree(pState->paSvnPropChanges[i].pszValue);
-        pState->paSvnPropChanges[i].pszValue = NULL;
-        return VERR_NO_MEMORY;
-    }
-    return VINF_SUCCESS;
-}
-
-
-/**
- * Schedules a property deletion.
- *
- * @returns IPRT status code.
- * @param   pState              The rewrite state to work on.
- * @param   pszName             The name of the property to delete.
- */
-static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
-{
-    return scmSvnSetProperty(pState, pszName, NULL);
-}
-
-
-/**
- * Applies any SVN property changes to the work copy of the file.
- *
- * @returns IPRT status code.
- * @param   pState              The rewrite state which SVN property changes
- *                              should be applied.
- */
-static int scmSvnDisplayChanges(PSCMRWSTATE pState)
-{
-    size_t i = pState->cSvnPropChanges;
-    while (i-- > 0)
-    {
-        const char *pszName  = pState->paSvnPropChanges[i].pszName;
-        const char *pszValue = pState->paSvnPropChanges[i].pszValue;
-        if (pszValue)
-            ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
-        else
-            ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
-    }
-
-    return VINF_SUCCESS;
-}
-
-/**
- * Applies any SVN property changes to the work copy of the file.
- *
- * @returns IPRT status code.
- * @param   pState              The rewrite state which SVN property changes
- *                              should be applied.
- */
-static int scmSvnApplyChanges(PSCMRWSTATE pState)
-{
-#ifdef SCM_WITHOUT_LIBSVN
-    /*
-     * This sucks. We gotta find svn(.exe).
-     */
-    static char s_szSvnPath[RTPATH_MAX];
-    if (s_szSvnPath[0] == '\0')
-        scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
-
-    /*
-     * Iterate thru the changes and apply them by starting the svn client.
-     */
-    for (size_t i = 0; i <pState->cSvnPropChanges; i++)
-    {
-        const char *apszArgv[6];
-        apszArgv[0] = s_szSvnPath;
-        apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
-        apszArgv[2] = pState->paSvnPropChanges[i].pszName;
-        int iArg = 3;
-        if (pState->paSvnPropChanges[i].pszValue)
-            apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
-        apszArgv[iArg++] = pState->pszFilename;
-        apszArgv[iArg++] = NULL;
-        ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
-                   apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
-
-        RTPROCESS pid;
-        int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
-        if (RT_SUCCESS(rc))
-        {
-            RTPROCSTATUS Status;
-            rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
-            if (    RT_SUCCESS(rc)
-                &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
-                     || Status.iStatus != 0) )
-            {
-                RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
-                           pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
-                           Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
-                           : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
-                           : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
-                           : "abducted by alien",
-                           Status.iStatus);
-                return VERR_GENERAL_FAILURE;
-            }
-        }
-        if (RT_FAILURE(rc))
-        {
-            RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
-                       pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
-            return rc;
-        }
-    }
-
-    return VINF_SUCCESS;
-#else
-    return VERR_NOT_IMPLEMENTED;
-#endif
-}
-
-
-/* -=-=-=-=-=- rewriters -=-=-=-=-=- */
-
-
-/**
- * Strip trailing blanks (space & tab).
- *
- * @returns True if modified, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    if (!pSettings->fStripTrailingBlanks)
-        return false;
-
-    bool        fModified = false;
-    SCMEOL      enmEol;
-    size_t      cchLine;
-    const char *pchLine;
-    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
-    {
-        int rc;
-        if (    cchLine == 0
-            ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
-            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
-        else
-        {
-            cchLine--;
-            while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
-                cchLine--;
-            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
-            fModified = true;
-        }
-        if (RT_FAILURE(rc))
-            return false;
-    }
-    if (fModified)
-        ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
-    return fModified;
-}
-
-/**
- * Expand tabs.
- *
- * @returns True if modified, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    if (!pSettings->fConvertTabs)
-        return false;
-
-    size_t const    cchTab = pSettings->cchTab;
-    bool            fModified = false;
-    SCMEOL          enmEol;
-    size_t          cchLine;
-    const char     *pchLine;
-    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
-    {
-        int rc;
-        const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
-        if (!pchTab)
-            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
-        else
-        {
-            size_t      offTab   = 0;
-            const char *pchChunk = pchLine;
-            for (;;)
-            {
-                size_t  cchChunk = pchTab - pchChunk;
-                offTab += cchChunk;
-                ScmStreamWrite(pOut, pchChunk, cchChunk);
-
-                size_t  cchToTab = cchTab - offTab % cchTab;
-                ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
-                offTab += cchToTab;
-
-                pchChunk = pchTab + 1;
-                size_t  cchLeft  = cchLine - (pchChunk - pchLine);
-                pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
-                if (!pchTab)
-                {
-                    rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
-                    break;
-                }
-            }
-
-            fModified = true;
-        }
-        if (RT_FAILURE(rc))
-            return false;
-    }
-    if (fModified)
-        ScmVerbose(pState, 2, " * Expanded tabs\n");
-    return fModified;
-}
-
-/**
- * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- * @param   enmDesiredEol       The desired end of line indicator type.
- * @param   pszDesiredSvnEol    The desired svn:eol-style.
- */
-static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
-                             SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
-{
-    if (!pSettings->fConvertEol)
-        return false;
-
-    bool        fModified = false;
-    SCMEOL      enmEol;
-    size_t      cchLine;
-    const char *pchLine;
-    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
-    {
-        if (   enmEol != enmDesiredEol
-            && enmEol != SCMEOL_NONE)
-        {
-            fModified = true;
-            enmEol = enmDesiredEol;
-        }
-        int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
-        if (RT_FAILURE(rc))
-            return false;
-    }
-    if (fModified)
-        ScmVerbose(pState, 2, " * Converted EOL markers\n");
-
-    /* Check svn:eol-style if appropriate */
-    if (   pSettings->fSetSvnEol
-        && scmSvnIsInWorkingCopy(pState))
-    {
-        char *pszEol;
-        int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
-        if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
-            || rc == VERR_NOT_FOUND)
-        {
-            if (rc == VERR_NOT_FOUND)
-                ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
-            else
-                ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
-            int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
-            if (RT_FAILURE(rc2))
-                RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
-        }
-        if (RT_SUCCESS(rc))
-            RTStrFree(pszEol);
-    }
-
-    /** @todo also check the subversion svn:eol-style state! */
-    return fModified;
-}
-
-/**
- * Force native end of line indicator.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
-    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
-#else
-    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
-#endif
-}
-
-/**
- * Force the stream to use LF as the end of line indicator.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
-}
-
-/**
- * Force the stream to use CRLF as the end of line indicator.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
-}
-
-/**
- * Strip trailing blank lines and/or make sure there is exactly one blank line
- * at the end of the file.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- *
- * @remarks ASSUMES trailing white space has been removed already.
- */
-static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    if (   !pSettings->fStripTrailingLines
-        && !pSettings->fForceTrailingLine
-        && !pSettings->fForceFinalEol)
-        return false;
-
-    size_t const cLines = ScmStreamCountLines(pIn);
-
-    /* Empty files remains empty. */
-    if (cLines <= 1)
-        return false;
-
-    /* Figure out if we need to adjust the number of lines or not. */
-    size_t cLinesNew = cLines;
-
-    if (   pSettings->fStripTrailingLines
-        && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
-    {
-        while (   cLinesNew > 1
-               && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
-            cLinesNew--;
-    }
-
-    if (    pSettings->fForceTrailingLine
-        && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
-        cLinesNew++;
-
-    bool fFixMissingEol = pSettings->fForceFinalEol
-                       && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
-
-    if (   !fFixMissingEol
-        && cLines == cLinesNew)
-        return false;
-
-    /* Copy the number of lines we've arrived at. */
-    ScmStreamRewindForReading(pIn);
-
-    size_t cCopied = RT_MIN(cLinesNew, cLines);
-    ScmStreamCopyLines(pOut, pIn, cCopied);
-
-    if (cCopied != cLinesNew)
-    {
-        while (cCopied++ < cLinesNew)
-            ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
-    }
-    /* Fix missing EOL if required. */
-    else if (fFixMissingEol)
-    {
-        if (ScmStreamGetEol(pIn) == SCMEOL_LF)
-            ScmStreamWrite(pOut, "\n", 1);
-        else
-            ScmStreamWrite(pOut, "\r\n", 2);
-    }
-
-    ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
-    return true;
-}
-
-/**
- * Make sure there is no svn:executable keyword on the current file.
- *
- * @returns false - the state carries these kinds of changes.
- * @param   pState              The rewriter state.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    if (   !pSettings->fSetSvnExecutable
-        || !scmSvnIsInWorkingCopy(pState))
-        return false;
-
-    int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
-    if (RT_SUCCESS(rc))
-    {
-        ScmVerbose(pState, 2, " * removing svn:executable\n");
-        rc = scmSvnDelProperty(pState, "svn:executable");
-        if (RT_FAILURE(rc))
-            RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
-    }
-    return false;
-}
-
-/**
- * Make sure the Id and Revision keywords are expanded.
- *
- * @returns false - the state carries these kinds of changes.
- * @param   pState              The rewriter state.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    if (   !pSettings->fSetSvnKeywords
-        || !scmSvnIsInWorkingCopy(pState))
-        return false;
-
-    char *pszKeywords;
-    int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
-    if (    RT_SUCCESS(rc)
-        && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
-            || !strstr(pszKeywords, "Revision")) )
-    {
-        if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
-            rc = RTStrAAppend(&pszKeywords, " Id Revision");
-        else if (!strstr(pszKeywords, "Id"))
-            rc = RTStrAAppend(&pszKeywords, " Id");
-        else
-            rc = RTStrAAppend(&pszKeywords, " Revision");
-        if (RT_SUCCESS(rc))
-        {
-            ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
-            rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
-            if (RT_FAILURE(rc))
-                RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
-        }
-        else
-            RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
-        RTStrFree(pszKeywords);
-    }
-    else if (rc == VERR_NOT_FOUND)
-    {
-        ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
-        rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
-        if (RT_FAILURE(rc))
-            RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
-    }
-    else if (RT_SUCCESS(rc))
-        RTStrFree(pszKeywords);
-
-    return false;
-}
-
-/**
- * Makefile.kup are empty files, enforce this.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- */
-static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    /* These files should be zero bytes. */
-    if (pIn->cb == 0)
-        return false;
-    ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
-    return true;
-}
-
-/**
- * Rewrite a kBuild makefile.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- *
- * @todo
- *
- * Ideas for Makefile.kmk and Config.kmk:
- *      - sort if1of/ifn1of sets.
- *      - line continuation slashes should only be preceded by one space.
- */
-static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-    return false;
-}
-
-/**
- * Rewrite a C/C++ source or header file.
- *
- * @returns true if modifications were made, false if not.
- * @param   pIn                 The input stream.
- * @param   pOut                The output stream.
- * @param   pSettings           The settings.
- *
- * @todo
- *
- * Ideas for C/C++:
- *      - space after if, while, for, switch
- *      - spaces in for (i=0;i<x;i++)
- *      - complex conditional, bird style.
- *      - remove unnecessary parentheses.
- *      - sort defined RT_OS_*||  and RT_ARCH
- *      - sizeof without parenthesis.
- *      - defined without parenthesis.
- *      - trailing spaces.
- *      - parameter indentation.
- *      - space after comma.
- *      - while (x--); -> multi line + comment.
- *      - else statement;
- *      - space between function and left parenthesis.
- *      - TODO, XXX, @todo cleanup.
- *      - Space before/after '*'.
- *      - ensure new line at end of file.
- *      - Indentation of precompiler statements (#ifdef, #defines).
- *      - space between functions.
- *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
- */
-static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
-{
-
-    return false;
-}
-
 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
+
 
 /**
@@ -2074,5 +1011,5 @@
     }
     if (   pBaseSettings->fOnlySvnFiles
-        && !scmSvnIsInWorkingCopy(pState))
+        && !ScmSvnIsInWorkingCopy(pState))
     {
         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
@@ -2184,10 +1121,10 @@
                             if (!g_fDryRun)
                             {
-                                rc = scmSvnApplyChanges(pState);
+                                rc = ScmSvnApplyChanges(pState);
                                 if (RT_FAILURE(rc))
                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
                             }
                             else
-                                scmSvnDisplayChanges(pState);
+                                ScmSvnDisplayChanges(pState);
                         }
 
Index: /trunk/src/bldprogs/scm.h
===================================================================
--- /trunk/src/bldprogs/scm.h	(revision 40534)
+++ /trunk/src/bldprogs/scm.h	(revision 40534)
@@ -0,0 +1,225 @@
+/* $Id$ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+#ifndef ___scm_h___
+#define ___scm_h___
+
+#include "scmstream.h"
+
+RT_C_DECLS_BEGIN
+
+/** Pointer to the rewriter state. */
+typedef struct SCMRWSTATE *PSCMRWSTATE;
+/** Pointer to const massager settings. */
+typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
+
+
+/** @name Subversion Access
+ * @{  */
+
+/**
+ * SVN property.
+ */
+typedef struct SCMSVNPROP
+{
+    /** The property. */
+    char           *pszName;
+    /** The value.
+     * When used to record updates, this can be set to NULL to trigger the
+     * deletion of the property. */
+    char           *pszValue;
+} SCMSVNPROP;
+/** Pointer to a SVN property. */
+typedef SCMSVNPROP *PSCMSVNPROP;
+/** Pointer to a const  SVN property. */
+typedef SCMSVNPROP const *PCSCMSVNPROP;
+
+
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState);
+int  ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue);
+int  ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue);
+int  ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName);
+int  ScmSvnDisplayChanges(PSCMRWSTATE pState);
+int  ScmSvnApplyChanges(PSCMRWSTATE pState);
+
+/** @} */
+
+
+/** @name Rewriters
+ * @{ */
+
+/**
+ * Rewriter state.
+ */
+typedef struct SCMRWSTATE
+{
+    /** The filename.  */
+    const char         *pszFilename;
+    /** Set after the printing the first verbose message about a file under
+     *  rewrite. */
+    bool                fFirst;
+    /** The number of SVN property changes. */
+    size_t              cSvnPropChanges;
+    /** Pointer to an array of SVN property changes. */
+    struct SCMSVNPROP  *paSvnPropChanges;
+} SCMRWSTATE;
+
+/**
+ * A rewriter.
+ *
+ * This works like a stream editor, reading @a pIn, modifying it and writing it
+ * to @a pOut.
+ *
+ * @returns true if any changes were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+typedef bool FNSCMREWRITER(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
+/** Pointer to a rewriter method. */
+typedef FNSCMREWRITER *PFNSCMREWRITER;
+
+FNSCMREWRITER rewrite_StripTrailingBlanks;
+FNSCMREWRITER rewrite_ExpandTabs;
+FNSCMREWRITER rewrite_ForceNativeEol;
+FNSCMREWRITER rewrite_ForceLF;
+FNSCMREWRITER rewrite_ForceCRLF;
+FNSCMREWRITER rewrite_AdjustTrailingLines;
+FNSCMREWRITER rewrite_SvnNoExecutable;
+FNSCMREWRITER rewrite_SvnKeywords;
+FNSCMREWRITER rewrite_Makefile_kup;
+FNSCMREWRITER rewrite_Makefile_kmk;
+FNSCMREWRITER rewrite_C_and_CPP;
+
+/** @}  */
+
+
+/** @name Settings
+ * @{ */
+
+/**
+ * Configuration entry.
+ */
+typedef struct SCMCFGENTRY
+{
+    /** Number of rewriters. */
+    size_t          cRewriters;
+    /** Pointer to an array of rewriters. */
+    PFNSCMREWRITER const  *papfnRewriter;
+    /** File pattern (simple).  */
+    const char     *pszFilePattern;
+} SCMCFGENTRY;
+typedef SCMCFGENTRY *PSCMCFGENTRY;
+typedef SCMCFGENTRY const *PCSCMCFGENTRY;
+
+
+/**
+ * Source Code Massager Settings.
+ */
+typedef struct SCMSETTINGSBASE
+{
+    bool            fConvertEol;
+    bool            fConvertTabs;
+    bool            fForceFinalEol;
+    bool            fForceTrailingLine;
+    bool            fStripTrailingBlanks;
+    bool            fStripTrailingLines;
+    /** Only process files that are part of a SVN working copy. */
+    bool            fOnlySvnFiles;
+    /** Only recurse into directories containing an .svn dir.  */
+    bool            fOnlySvnDirs;
+    /** Set svn:eol-style if missing or incorrect. */
+    bool            fSetSvnEol;
+    /** Set svn:executable according to type (unusually this means deleting it). */
+    bool            fSetSvnExecutable;
+    /** Set svn:keyword if completely or partially missing. */
+    bool            fSetSvnKeywords;
+    /**  */
+    unsigned        cchTab;
+    /** Only consider files matching these patterns.  This is only applied to the
+     *  base names. */
+    char           *pszFilterFiles;
+    /** Filter out files matching the following patterns.  This is applied to base
+     *  names as well as the absolute paths.  */
+    char           *pszFilterOutFiles;
+    /** Filter out directories matching the following patterns.  This is applied
+     *  to base names as well as the absolute paths.  All absolute paths ends with a
+     *  slash and dot ("/.").  */
+    char           *pszFilterOutDirs;
+} SCMSETTINGSBASE;
+/** Pointer to massager settings. */
+typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
+
+/**
+ * File/dir pattern + options.
+ */
+typedef struct SCMPATRNOPTPAIR
+{
+    char *pszPattern;
+    char *pszOptions;
+} SCMPATRNOPTPAIR;
+/** Pointer to a pattern + option pair. */
+typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
+
+
+/** Pointer to a settings set. */
+typedef struct SCMSETTINGS *PSCMSETTINGS;
+/**
+ * Settings set.
+ *
+ * This structure is constructed from the command line arguments or any
+ * .scm-settings file found in a directory we recurse into.  When recursing in
+ * and out of a directory, we push and pop a settings set for it.
+ *
+ * The .scm-settings file has two kinds of setttings, first there are the
+ * unqualified base settings and then there are the settings which applies to a
+ * set of files or directories.  The former are lines with command line options.
+ * For the latter, the options are preceded by a string pattern and a colon.
+ * The pattern specifies which files (and/or directories) the options applies
+ * to.
+ *
+ * We parse the base options into the Base member and put the others into the
+ * paPairs array.
+ */
+typedef struct SCMSETTINGS
+{
+    /** Pointer to the setting file below us in the stack. */
+    PSCMSETTINGS        pDown;
+    /** Pointer to the setting file above us in the stack. */
+    PSCMSETTINGS        pUp;
+    /** File/dir patterns and their options. */
+    PSCMPATRNOPTPAIR    paPairs;
+    /** The number of entires in paPairs. */
+    uint32_t            cPairs;
+    /** The base settings that was read out of the file. */
+    SCMSETTINGSBASE     Base;
+} SCMSETTINGS;
+/** Pointer to a const settings set. */
+typedef SCMSETTINGS const *PCSCMSETTINGS;
+
+/** @} */
+
+
+void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...);
+
+extern const char g_szTabSpaces[16+1];
+
+RT_C_DECLS_END
+
+#endif
+
Index: /trunk/src/bldprogs/scmrw.cpp
===================================================================
--- /trunk/src/bldprogs/scmrw.cpp	(revision 40534)
+++ /trunk/src/bldprogs/scmrw.cpp	(revision 40534)
@@ -0,0 +1,460 @@
+/* $Id$ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+
+/**
+ * Strip trailing blanks (space & tab).
+ *
+ * @returns True if modified, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    if (!pSettings->fStripTrailingBlanks)
+        return false;
+
+    bool        fModified = false;
+    SCMEOL      enmEol;
+    size_t      cchLine;
+    const char *pchLine;
+    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+    {
+        int rc;
+        if (    cchLine == 0
+            ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
+            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+        else
+        {
+            cchLine--;
+            while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
+                cchLine--;
+            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+            fModified = true;
+        }
+        if (RT_FAILURE(rc))
+            return false;
+    }
+    if (fModified)
+        ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
+    return fModified;
+}
+
+/**
+ * Expand tabs.
+ *
+ * @returns True if modified, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    if (!pSettings->fConvertTabs)
+        return false;
+
+    size_t const    cchTab = pSettings->cchTab;
+    bool            fModified = false;
+    SCMEOL          enmEol;
+    size_t          cchLine;
+    const char     *pchLine;
+    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+    {
+        int rc;
+        const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
+        if (!pchTab)
+            rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+        else
+        {
+            size_t      offTab   = 0;
+            const char *pchChunk = pchLine;
+            for (;;)
+            {
+                size_t  cchChunk = pchTab - pchChunk;
+                offTab += cchChunk;
+                ScmStreamWrite(pOut, pchChunk, cchChunk);
+
+                size_t  cchToTab = cchTab - offTab % cchTab;
+                ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
+                offTab += cchToTab;
+
+                pchChunk = pchTab + 1;
+                size_t  cchLeft  = cchLine - (pchChunk - pchLine);
+                pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
+                if (!pchTab)
+                {
+                    rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
+                    break;
+                }
+            }
+
+            fModified = true;
+        }
+        if (RT_FAILURE(rc))
+            return false;
+    }
+    if (fModified)
+        ScmVerbose(pState, 2, " * Expanded tabs\n");
+    return fModified;
+}
+
+/**
+ * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ * @param   enmDesiredEol       The desired end of line indicator type.
+ * @param   pszDesiredSvnEol    The desired svn:eol-style.
+ */
+static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
+                             SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
+{
+    if (!pSettings->fConvertEol)
+        return false;
+
+    bool        fModified = false;
+    SCMEOL      enmEol;
+    size_t      cchLine;
+    const char *pchLine;
+    while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+    {
+        if (   enmEol != enmDesiredEol
+            && enmEol != SCMEOL_NONE)
+        {
+            fModified = true;
+            enmEol = enmDesiredEol;
+        }
+        int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+        if (RT_FAILURE(rc))
+            return false;
+    }
+    if (fModified)
+        ScmVerbose(pState, 2, " * Converted EOL markers\n");
+
+    /* Check svn:eol-style if appropriate */
+    if (   pSettings->fSetSvnEol
+        && ScmSvnIsInWorkingCopy(pState))
+    {
+        char *pszEol;
+        int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
+        if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
+            || rc == VERR_NOT_FOUND)
+        {
+            if (rc == VERR_NOT_FOUND)
+                ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
+            else
+                ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
+            int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
+            if (RT_FAILURE(rc2))
+                RTMsgError("ScmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
+        }
+        if (RT_SUCCESS(rc))
+            RTStrFree(pszEol);
+    }
+
+    /** @todo also check the subversion svn:eol-style state! */
+    return fModified;
+}
+
+/**
+ * Force native end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
+#else
+    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
+#endif
+}
+
+/**
+ * Force the stream to use LF as the end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
+}
+
+/**
+ * Force the stream to use CRLF as the end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
+}
+
+/**
+ * Strip trailing blank lines and/or make sure there is exactly one blank line
+ * at the end of the file.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ *
+ * @remarks ASSUMES trailing white space has been removed already.
+ */
+bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    if (   !pSettings->fStripTrailingLines
+        && !pSettings->fForceTrailingLine
+        && !pSettings->fForceFinalEol)
+        return false;
+
+    size_t const cLines = ScmStreamCountLines(pIn);
+
+    /* Empty files remains empty. */
+    if (cLines <= 1)
+        return false;
+
+    /* Figure out if we need to adjust the number of lines or not. */
+    size_t cLinesNew = cLines;
+
+    if (   pSettings->fStripTrailingLines
+        && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+    {
+        while (   cLinesNew > 1
+               && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
+            cLinesNew--;
+    }
+
+    if (    pSettings->fForceTrailingLine
+        && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+        cLinesNew++;
+
+    bool fFixMissingEol = pSettings->fForceFinalEol
+                       && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
+
+    if (   !fFixMissingEol
+        && cLines == cLinesNew)
+        return false;
+
+    /* Copy the number of lines we've arrived at. */
+    ScmStreamRewindForReading(pIn);
+
+    size_t cCopied = RT_MIN(cLinesNew, cLines);
+    ScmStreamCopyLines(pOut, pIn, cCopied);
+
+    if (cCopied != cLinesNew)
+    {
+        while (cCopied++ < cLinesNew)
+            ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
+    }
+    /* Fix missing EOL if required. */
+    else if (fFixMissingEol)
+    {
+        if (ScmStreamGetEol(pIn) == SCMEOL_LF)
+            ScmStreamWrite(pOut, "\n", 1);
+        else
+            ScmStreamWrite(pOut, "\r\n", 2);
+    }
+
+    ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
+    return true;
+}
+
+/**
+ * Make sure there is no svn:executable keyword on the current file.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param   pState              The rewriter state.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    if (   !pSettings->fSetSvnExecutable
+        || !ScmSvnIsInWorkingCopy(pState))
+        return false;
+
+    int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
+    if (RT_SUCCESS(rc))
+    {
+        ScmVerbose(pState, 2, " * removing svn:executable\n");
+        rc = ScmSvnDelProperty(pState, "svn:executable");
+        if (RT_FAILURE(rc))
+            RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+    }
+    return false;
+}
+
+/**
+ * Make sure the Id and Revision keywords are expanded.
+ *
+ * @returns false - the state carries these kinds of changes.
+ * @param   pState              The rewriter state.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    if (   !pSettings->fSetSvnKeywords
+        || !ScmSvnIsInWorkingCopy(pState))
+        return false;
+
+    char *pszKeywords;
+    int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
+    if (    RT_SUCCESS(rc)
+        && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
+            || !strstr(pszKeywords, "Revision")) )
+    {
+        if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
+            rc = RTStrAAppend(&pszKeywords, " Id Revision");
+        else if (!strstr(pszKeywords, "Id"))
+            rc = RTStrAAppend(&pszKeywords, " Id");
+        else
+            rc = RTStrAAppend(&pszKeywords, " Revision");
+        if (RT_SUCCESS(rc))
+        {
+            ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
+            rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
+            if (RT_FAILURE(rc))
+                RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+        }
+        else
+            RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
+        RTStrFree(pszKeywords);
+    }
+    else if (rc == VERR_NOT_FOUND)
+    {
+        ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
+        rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
+        if (RT_FAILURE(rc))
+            RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+    }
+    else if (RT_SUCCESS(rc))
+        RTStrFree(pszKeywords);
+
+    return false;
+}
+
+/**
+ * Makefile.kup are empty files, enforce this.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ */
+bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    /* These files should be zero bytes. */
+    if (pIn->cb == 0)
+        return false;
+    ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
+    return true;
+}
+
+/**
+ * Rewrite a kBuild makefile.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ *
+ * @todo
+ *
+ * Ideas for Makefile.kmk and Config.kmk:
+ *      - sort if1of/ifn1of sets.
+ *      - line continuation slashes should only be preceded by one space.
+ */
+bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+    return false;
+}
+
+/**
+ * Rewrite a C/C++ source or header file.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param   pIn                 The input stream.
+ * @param   pOut                The output stream.
+ * @param   pSettings           The settings.
+ *
+ * @todo
+ *
+ * Ideas for C/C++:
+ *      - space after if, while, for, switch
+ *      - spaces in for (i=0;i<x;i++)
+ *      - complex conditional, bird style.
+ *      - remove unnecessary parentheses.
+ *      - sort defined RT_OS_*||  and RT_ARCH
+ *      - sizeof without parenthesis.
+ *      - defined without parenthesis.
+ *      - trailing spaces.
+ *      - parameter indentation.
+ *      - space after comma.
+ *      - while (x--); -> multi line + comment.
+ *      - else statement;
+ *      - space between function and left parenthesis.
+ *      - TODO, XXX, @todo cleanup.
+ *      - Space before/after '*'.
+ *      - ensure new line at end of file.
+ *      - Indentation of precompiler statements (#ifdef, #defines).
+ *      - space between functions.
+ *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
+ */
+bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+
+    return false;
+}
+
Index: /trunk/src/bldprogs/scmsubversion.cpp
===================================================================
--- /trunk/src/bldprogs/scmsubversion.cpp	(revision 40534)
+++ /trunk/src/bldprogs/scmsubversion.cpp	(revision 40534)
@@ -0,0 +1,511 @@
+/* $Id$ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define SCM_WITHOUT_LIBSVN
+
+/*******************************************************************************
+*   Header Files                                                               *
+*******************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+
+#ifdef SCM_WITHOUT_LIBSVN
+
+/**
+ * Callback that is call for each path to search.
+ */
+static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
+{
+    char   *pszDst = (char *)pvUser1;
+    size_t  cchDst = (size_t)pvUser2;
+    if (cchDst > cchPath)
+    {
+        memcpy(pszDst, pchPath, cchPath);
+        pszDst[cchPath] = '\0';
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+        int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
+#else
+        int rc = RTPathAppend(pszDst, cchDst, "svn");
+#endif
+        if (   RT_SUCCESS(rc)
+            && RTFileExists(pszDst))
+            return VINF_SUCCESS;
+    }
+    return VERR_TRY_AGAIN;
+}
+
+
+/**
+ * Finds the svn binary.
+ *
+ * @param   pszPath             Where to store it.  Worst case, we'll return
+ *                              "svn" here.
+ * @param   cchPath             The size of the buffer pointed to by @a pszPath.
+ */
+static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
+{
+    /** @todo code page fun... */
+    Assert(cchPath >= sizeof("svn"));
+#ifdef RT_OS_WINDOWS
+    const char *pszEnvVar = RTEnvGet("Path");
+#else
+    const char *pszEnvVar = RTEnvGet("PATH");
+#endif
+    if (pszPath)
+    {
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+        int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
+#else
+        int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
+#endif
+        if (RT_SUCCESS(rc))
+            return;
+    }
+    strcpy(pszPath, "svn");
+}
+
+
+/**
+ * Construct a dot svn filename for the file being rewritten.
+ *
+ * @returns IPRT status code.
+ * @param   pState              The rewrite state (for the name).
+ * @param   pszDir              The directory, including ".svn/".
+ * @param   pszSuff             The filename suffix.
+ * @param   pszDst              The output buffer.  RTPATH_MAX in size.
+ */
+static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
+{
+    strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
+    RTPathStripFilename(pszDst);
+
+    int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
+        if (RT_SUCCESS(rc))
+        {
+            size_t cchDst  = strlen(pszDst);
+            size_t cchSuff = strlen(pszSuff);
+            if (cchDst + cchSuff < RTPATH_MAX)
+            {
+                memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
+                return VINF_SUCCESS;
+            }
+            else
+                rc = VERR_BUFFER_OVERFLOW;
+        }
+    }
+    return rc;
+}
+
+/**
+ * Interprets the specified string as decimal numbers.
+ *
+ * @returns true if parsed successfully, false if not.
+ * @param   pch                 The string (not terminated).
+ * @param   cch                 The string length.
+ * @param   pu                  Where to return the value.
+ */
+static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
+{
+    size_t u = 0;
+    while (cch-- > 0)
+    {
+        char ch = *pch++;
+        if (ch < '0' || ch > '9')
+            return false;
+        u *= 10;
+        u += ch - '0';
+    }
+    *pu = u;
+    return true;
+}
+
+#endif /* SCM_WITHOUT_LIBSVN */
+
+/**
+ * Checks if the file we're operating on is part of a SVN working copy.
+ *
+ * @returns true if it is, false if it isn't or we cannot tell.
+ * @param   pState              The rewrite state to work on.
+ */
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
+{
+#ifdef SCM_WITHOUT_LIBSVN
+    /*
+     * Hack: check if the .svn/text-base/<file>.svn-base file exists.
+     */
+    char szPath[RTPATH_MAX];
+    int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
+    if (RT_SUCCESS(rc))
+        return RTFileExists(szPath);
+
+#else
+    NOREF(pState);
+#endif
+    return false;
+}
+
+/**
+ * Queries the value of an SVN property.
+ *
+ * This will automatically adjust for scheduled changes.
+ *
+ * @returns IPRT status code.
+ * @retval  VERR_INVALID_STATE if not a SVN WC file.
+ * @retval  VERR_NOT_FOUND if the property wasn't found.
+ * @param   pState              The rewrite state to work on.
+ * @param   pszName             The property name.
+ * @param   ppszValue           Where to return the property value.  Free this
+ *                              using RTStrFree.  Optional.
+ */
+int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
+{
+    /*
+     * Look it up in the scheduled changes.
+     */
+    uint32_t i = pState->cSvnPropChanges;
+    while (i-- > 0)
+        if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
+        {
+            const char *pszValue = pState->paSvnPropChanges[i].pszValue;
+            if (!pszValue)
+                return VERR_NOT_FOUND;
+            if (ppszValue)
+                return RTStrDupEx(ppszValue, pszValue);
+            return VINF_SUCCESS;
+        }
+
+#ifdef SCM_WITHOUT_LIBSVN
+    /*
+     * Hack: Read the .svn/props/<file>.svn-work file exists.
+     */
+    char szPath[RTPATH_MAX];
+    int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
+    if (RT_SUCCESS(rc) && !RTFileExists(szPath))
+        rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
+    if (RT_SUCCESS(rc))
+    {
+        SCMSTREAM Stream;
+        rc = ScmStreamInitForReading(&Stream, szPath);
+        if (RT_SUCCESS(rc))
+        {
+            /*
+             * The current format is K len\n<name>\nV len\n<value>\n" ... END.
+             */
+            rc = VERR_NOT_FOUND;
+            size_t const    cchName = strlen(pszName);
+            SCMEOL          enmEol;
+            size_t          cchLine;
+            const char     *pchLine;
+            while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
+            {
+                /*
+                 * Parse the 'K num' / 'END' line.
+                 */
+                if (   cchLine == 3
+                    && !memcmp(pchLine, "END", 3))
+                    break;
+                size_t cchKey;
+                if (   cchLine < 3
+                    || pchLine[0] != 'K'
+                    || pchLine[1] != ' '
+                    || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
+                    || cchKey == 0
+                    || cchKey > 4096)
+                {
+                    RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
+                    rc = VERR_PARSE_ERROR;
+                    break;
+                }
+
+                /*
+                 * Match the key and skip to the value line.  Don't bother with
+                 * names containing EOL markers.
+                 */
+                size_t const offKey = ScmStreamTell(&Stream);
+                bool fMatch = cchName == cchKey;
+                if (fMatch)
+                {
+                    pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
+                    if (!pchLine)
+                        break;
+                    fMatch = cchLine == cchName
+                          && !memcmp(pchLine, pszName, cchName);
+                }
+
+                if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
+                    break;
+                if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
+                    break;
+
+                /*
+                 * Read and Parse the 'V num' line.
+                 */
+                pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
+                if (!pchLine)
+                    break;
+                size_t cchValue;
+                if (   cchLine < 3
+                    || pchLine[0] != 'V'
+                    || pchLine[1] != ' '
+                    || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
+                    || cchValue > _1M)
+                {
+                    RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
+                    rc = VERR_PARSE_ERROR;
+                    break;
+                }
+
+                /*
+                 * If we have a match, allocate a return buffer and read the
+                 * value into it.  Otherwise skip this value and continue
+                 * searching.
+                 */
+                if (fMatch)
+                {
+                    if (!ppszValue)
+                        rc = VINF_SUCCESS;
+                    else
+                    {
+                        char *pszValue;
+                        rc = RTStrAllocEx(&pszValue, cchValue + 1);
+                        if (RT_SUCCESS(rc))
+                        {
+                            rc = ScmStreamRead(&Stream, pszValue, cchValue);
+                            if (RT_SUCCESS(rc))
+                                *ppszValue = pszValue;
+                            else
+                                RTStrFree(pszValue);
+                        }
+                    }
+                    break;
+                }
+
+                if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
+                    break;
+                if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
+                    break;
+            }
+
+            if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
+            {
+                rc = ScmStreamGetStatus(&Stream);
+                RTMsgError("%s: stream error %Rrc\n", szPath, rc);
+            }
+            ScmStreamDelete(&Stream);
+        }
+    }
+
+    if (rc == VERR_FILE_NOT_FOUND)
+        rc = VERR_NOT_FOUND;
+    return rc;
+
+#else
+    NOREF(pState);
+#endif
+    return VERR_NOT_FOUND;
+}
+
+
+/**
+ * Schedules the setting of a property.
+ *
+ * @returns IPRT status code.
+ * @retval  VERR_INVALID_STATE if not a SVN WC file.
+ * @param   pState              The rewrite state to work on.
+ * @param   pszName             The name of the property to set.
+ * @param   pszValue            The value.  NULL means deleting it.
+ */
+int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
+{
+    /*
+     * Update any existing entry first.
+     */
+    size_t i = pState->cSvnPropChanges;
+    while (i-- > 0)
+        if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
+        {
+            if (!pszValue)
+            {
+                RTStrFree(pState->paSvnPropChanges[i].pszValue);
+                pState->paSvnPropChanges[i].pszValue = NULL;
+            }
+            else
+            {
+                char *pszCopy;
+                int rc = RTStrDupEx(&pszCopy, pszValue);
+                if (RT_FAILURE(rc))
+                    return rc;
+                pState->paSvnPropChanges[i].pszValue = pszCopy;
+            }
+            return VINF_SUCCESS;
+        }
+
+    /*
+     * Insert a new entry.
+     */
+    i = pState->cSvnPropChanges;
+    if ((i % 32) == 0)
+    {
+        void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
+        if (!pvNew)
+            return VERR_NO_MEMORY;
+        pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
+    }
+
+    pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
+    pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
+    if (   pState->paSvnPropChanges[i].pszName
+        && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
+        pState->cSvnPropChanges = i + 1;
+    else
+    {
+        RTStrFree(pState->paSvnPropChanges[i].pszName);
+        pState->paSvnPropChanges[i].pszName = NULL;
+        RTStrFree(pState->paSvnPropChanges[i].pszValue);
+        pState->paSvnPropChanges[i].pszValue = NULL;
+        return VERR_NO_MEMORY;
+    }
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Schedules a property deletion.
+ *
+ * @returns IPRT status code.
+ * @param   pState              The rewrite state to work on.
+ * @param   pszName             The name of the property to delete.
+ */
+int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
+{
+    return ScmSvnSetProperty(pState, pszName, NULL);
+}
+
+
+/**
+ * Applies any SVN property changes to the work copy of the file.
+ *
+ * @returns IPRT status code.
+ * @param   pState              The rewrite state which SVN property changes
+ *                              should be applied.
+ */
+int ScmSvnDisplayChanges(PSCMRWSTATE pState)
+{
+    size_t i = pState->cSvnPropChanges;
+    while (i-- > 0)
+    {
+        const char *pszName  = pState->paSvnPropChanges[i].pszName;
+        const char *pszValue = pState->paSvnPropChanges[i].pszValue;
+        if (pszValue)
+            ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
+        else
+            ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Applies any SVN property changes to the work copy of the file.
+ *
+ * @returns IPRT status code.
+ * @param   pState              The rewrite state which SVN property changes
+ *                              should be applied.
+ */
+int ScmSvnApplyChanges(PSCMRWSTATE pState)
+{
+#ifdef SCM_WITHOUT_LIBSVN
+    /*
+     * This sucks. We gotta find svn(.exe).
+     */
+    static char s_szSvnPath[RTPATH_MAX];
+    if (s_szSvnPath[0] == '\0')
+        scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
+
+    /*
+     * Iterate thru the changes and apply them by starting the svn client.
+     */
+    for (size_t i = 0; i < pState->cSvnPropChanges; i++)
+    {
+        const char *apszArgv[6];
+        apszArgv[0] = s_szSvnPath;
+        apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
+        apszArgv[2] = pState->paSvnPropChanges[i].pszName;
+        int iArg = 3;
+        if (pState->paSvnPropChanges[i].pszValue)
+            apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
+        apszArgv[iArg++] = pState->pszFilename;
+        apszArgv[iArg++] = NULL;
+        ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
+                   apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
+
+        RTPROCESS pid;
+        int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
+        if (RT_SUCCESS(rc))
+        {
+            RTPROCSTATUS Status;
+            rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
+            if (    RT_SUCCESS(rc)
+                &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
+                     || Status.iStatus != 0) )
+            {
+                RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
+                           pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
+                           Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
+                           : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
+                           : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
+                           : "abducted by alien",
+                           Status.iStatus);
+                return VERR_GENERAL_FAILURE;
+            }
+        }
+        if (RT_FAILURE(rc))
+        {
+            RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
+                       pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
+            return rc;
+        }
+    }
+
+    return VINF_SUCCESS;
+#else
+    return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+
