VirtualBox

Changeset 40534 in vbox for trunk/src/bldprogs/scmrw.cpp


Ignore:
Timestamp:
Mar 19, 2012 11:49:34 AM (13 years ago)
Author:
vboxsync
Message:

scm: more splitting and some header merging.

File:
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/bldprogs/scmrw.cpp

    r40530 r40534  
    3535#include <iprt/string.h>
    3636
    37 #include "scmstream.h"
    38 #include "scmdiff.h"
    39 
    40 
    41 /*******************************************************************************
    42 *   Defined Constants And Macros                                               *
    43 *******************************************************************************/
    44 /** The name of the settings files. */
    45 #define SCM_SETTINGS_FILENAME           ".scm-settings"
    46 
    47 
    48 /*******************************************************************************
    49 *   Structures and Typedefs                                                    *
    50 *******************************************************************************/
    51 /** Pointer to const massager settings. */
    52 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
    53 
    54 /**
    55  * SVN property.
    56  */
    57 typedef struct SCMSVNPROP
    58 {
    59     /** The property. */
    60     char           *pszName;
    61     /** The value.
    62      * When used to record updates, this can be set to NULL to trigger the
    63      * deletion of the property. */
    64     char           *pszValue;
    65 } SCMSVNPROP;
    66 /** Pointer to a SVN property. */
    67 typedef SCMSVNPROP *PSCMSVNPROP;
    68 /** Pointer to a const  SVN property. */
    69 typedef SCMSVNPROP const *PCSCMSVNPROP;
    70 
    71 
    72 /**
    73  * Rewriter state.
    74  */
    75 typedef struct SCMRWSTATE
    76 {
    77     /** The filename.  */
    78     const char     *pszFilename;
    79     /** Set after the printing the first verbose message about a file under
    80      *  rewrite. */
    81     bool            fFirst;
    82     /** The number of SVN property changes. */
    83     size_t          cSvnPropChanges;
    84     /** Pointer to an array of SVN property changes. */
    85     PSCMSVNPROP     paSvnPropChanges;
    86 } SCMRWSTATE;
    87 /** Pointer to the rewriter state. */
    88 typedef SCMRWSTATE *PSCMRWSTATE;
    89 
    90 /**
    91  * A rewriter.
    92  *
    93  * This works like a stream editor, reading @a pIn, modifying it and writing it
    94  * to @a pOut.
    95  *
    96  * @returns true if any changes were made, false if not.
    97  * @param   pIn                 The input stream.
    98  * @param   pOut                The output stream.
    99  * @param   pSettings           The settings.
    100  */
    101 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    102 
    103 
    104 /**
    105  * Configuration entry.
    106  */
    107 typedef struct SCMCFGENTRY
    108 {
    109     /** Number of rewriters. */
    110     size_t          cRewriters;
    111     /** Pointer to an array of rewriters. */
    112     PFNSCMREWRITER const  *papfnRewriter;
    113     /** File pattern (simple).  */
    114     const char     *pszFilePattern;
    115 } SCMCFGENTRY;
    116 typedef SCMCFGENTRY *PSCMCFGENTRY;
    117 typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    118 
    119 
    120 /**
    121  * Source Code Massager Settings.
    122  */
    123 typedef struct SCMSETTINGSBASE
    124 {
    125     bool            fConvertEol;
    126     bool            fConvertTabs;
    127     bool            fForceFinalEol;
    128     bool            fForceTrailingLine;
    129     bool            fStripTrailingBlanks;
    130     bool            fStripTrailingLines;
    131     /** Only process files that are part of a SVN working copy. */
    132     bool            fOnlySvnFiles;
    133     /** Only recurse into directories containing an .svn dir.  */
    134     bool            fOnlySvnDirs;
    135     /** Set svn:eol-style if missing or incorrect. */
    136     bool            fSetSvnEol;
    137     /** Set svn:executable according to type (unusually this means deleting it). */
    138     bool            fSetSvnExecutable;
    139     /** Set svn:keyword if completely or partially missing. */
    140     bool            fSetSvnKeywords;
    141     /**  */
    142     unsigned        cchTab;
    143     /** Only consider files matching these patterns.  This is only applied to the
    144      *  base names. */
    145     char           *pszFilterFiles;
    146     /** Filter out files matching the following patterns.  This is applied to base
    147      *  names as well as the absolute paths.  */
    148     char           *pszFilterOutFiles;
    149     /** Filter out directories matching the following patterns.  This is applied
    150      *  to base names as well as the absolute paths.  All absolute paths ends with a
    151      *  slash and dot ("/.").  */
    152     char           *pszFilterOutDirs;
    153 } SCMSETTINGSBASE;
    154 /** Pointer to massager settings. */
    155 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
    156 
    157 /**
    158  * Option identifiers.
    159  *
    160  * @note    The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
    161  *          clear.  So, the option setting a flag (boolean) will have an even
    162  *          number and the one clearing it will have an odd number.
    163  * @note    Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
    164  */
    165 typedef enum SCMOPT
    166 {
    167     SCMOPT_CONVERT_EOL = 10000,
    168     SCMOPT_NO_CONVERT_EOL,
    169     SCMOPT_CONVERT_TABS,
    170     SCMOPT_NO_CONVERT_TABS,
    171     SCMOPT_FORCE_FINAL_EOL,
    172     SCMOPT_NO_FORCE_FINAL_EOL,
    173     SCMOPT_FORCE_TRAILING_LINE,
    174     SCMOPT_NO_FORCE_TRAILING_LINE,
    175     SCMOPT_STRIP_TRAILING_BLANKS,
    176     SCMOPT_NO_STRIP_TRAILING_BLANKS,
    177     SCMOPT_STRIP_TRAILING_LINES,
    178     SCMOPT_NO_STRIP_TRAILING_LINES,
    179     SCMOPT_ONLY_SVN_DIRS,
    180     SCMOPT_NOT_ONLY_SVN_DIRS,
    181     SCMOPT_ONLY_SVN_FILES,
    182     SCMOPT_NOT_ONLY_SVN_FILES,
    183     SCMOPT_SET_SVN_EOL,
    184     SCMOPT_DONT_SET_SVN_EOL,
    185     SCMOPT_SET_SVN_EXECUTABLE,
    186     SCMOPT_DONT_SET_SVN_EXECUTABLE,
    187     SCMOPT_SET_SVN_KEYWORDS,
    188     SCMOPT_DONT_SET_SVN_KEYWORDS,
    189     SCMOPT_TAB_SIZE,
    190     SCMOPT_FILTER_OUT_DIRS,
    191     SCMOPT_FILTER_FILES,
    192     SCMOPT_FILTER_OUT_FILES,
    193     SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
    194     //
    195     SCMOPT_DIFF_IGNORE_EOL,
    196     SCMOPT_DIFF_NO_IGNORE_EOL,
    197     SCMOPT_DIFF_IGNORE_SPACE,
    198     SCMOPT_DIFF_NO_IGNORE_SPACE,
    199     SCMOPT_DIFF_IGNORE_LEADING_SPACE,
    200     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
    201     SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
    202     SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
    203     SCMOPT_DIFF_SPECIAL_CHARS,
    204     SCMOPT_DIFF_NO_SPECIAL_CHARS,
    205     SCMOPT_END
    206 } SCMOPT;
    207 
    208 
    209 /**
    210  * File/dir pattern + options.
    211  */
    212 typedef struct SCMPATRNOPTPAIR
    213 {
    214     char *pszPattern;
    215     char *pszOptions;
    216 } SCMPATRNOPTPAIR;
    217 /** Pointer to a pattern + option pair. */
    218 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
    219 
    220 
    221 /** Pointer to a settings set. */
    222 typedef struct SCMSETTINGS *PSCMSETTINGS;
    223 /**
    224  * Settings set.
    225  *
    226  * This structure is constructed from the command line arguments or any
    227  * .scm-settings file found in a directory we recurse into.  When recursing in
    228  * and out of a directory, we push and pop a settings set for it.
    229  *
    230  * The .scm-settings file has two kinds of setttings, first there are the
    231  * unqualified base settings and then there are the settings which applies to a
    232  * set of files or directories.  The former are lines with command line options.
    233  * For the latter, the options are preceded by a string pattern and a colon.
    234  * The pattern specifies which files (and/or directories) the options applies
    235  * to.
    236  *
    237  * We parse the base options into the Base member and put the others into the
    238  * paPairs array.
    239  */
    240 typedef struct SCMSETTINGS
    241 {
    242     /** Pointer to the setting file below us in the stack. */
    243     PSCMSETTINGS        pDown;
    244     /** Pointer to the setting file above us in the stack. */
    245     PSCMSETTINGS        pUp;
    246     /** File/dir patterns and their options. */
    247     PSCMPATRNOPTPAIR    paPairs;
    248     /** The number of entires in paPairs. */
    249     uint32_t            cPairs;
    250     /** The base settings that was read out of the file. */
    251     SCMSETTINGSBASE     Base;
    252 } SCMSETTINGS;
    253 /** Pointer to a const settings set. */
    254 typedef SCMSETTINGS const *PCSCMSETTINGS;
    255 
    256 
    257 /*******************************************************************************
    258 *   Internal Functions                                                         *
    259 *******************************************************************************/
    260 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    261 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    262 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    263 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    264 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    265 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    266 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    267 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    268 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    269 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    270 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    271 
    272 
    273 /*******************************************************************************
    274 *   Global Variables                                                           *
    275 *******************************************************************************/
    276 static const char   g_szProgName[]          = "scm";
    277 static const char  *g_pszChangedSuff        = "";
    278 static const char   g_szTabSpaces[16+1]     = "                ";
    279 static bool         g_fDryRun               = true;
    280 static bool         g_fDiffSpecialChars     = true;
    281 static bool         g_fDiffIgnoreEol        = false;
    282 static bool         g_fDiffIgnoreLeadingWS  = false;
    283 static bool         g_fDiffIgnoreTrailingWS = false;
    284 static int          g_iVerbosity            = 2;//99; //0;
    285 
    286 /** The global settings. */
    287 static SCMSETTINGSBASE const g_Defaults =
    288 {
    289     /* .fConvertEol = */            true,
    290     /* .fConvertTabs = */           true,
    291     /* .fForceFinalEol = */         true,
    292     /* .fForceTrailingLine = */     false,
    293     /* .fStripTrailingBlanks = */   true,
    294     /* .fStripTrailingLines = */    true,
    295     /* .fOnlySvnFiles = */          false,
    296     /* .fOnlySvnDirs = */           false,
    297     /* .fSetSvnEol = */             false,
    298     /* .fSetSvnExecutable = */      false,
    299     /* .fSetSvnKeywords = */        false,
    300     /* .cchTab = */                 8,
    301     /* .pszFilterFiles = */         (char *)"",
    302     /* .pszFilterOutFiles = */      (char *)"*.exe|*.com|20*-*-*.log",
    303     /* .pszFilterOutDirs = */       (char *)".svn|.hg|.git|CVS",
    304 };
    305 
    306 /** Option definitions for the base settings. */
    307 static RTGETOPTDEF  g_aScmOpts[] =
    308 {
    309     { "--convert-eol",                      SCMOPT_CONVERT_EOL,                     RTGETOPT_REQ_NOTHING },
    310     { "--no-convert-eol",                   SCMOPT_NO_CONVERT_EOL,                  RTGETOPT_REQ_NOTHING },
    311     { "--convert-tabs",                     SCMOPT_CONVERT_TABS,                    RTGETOPT_REQ_NOTHING },
    312     { "--no-convert-tabs",                  SCMOPT_NO_CONVERT_TABS,                 RTGETOPT_REQ_NOTHING },
    313     { "--force-final-eol",                  SCMOPT_FORCE_FINAL_EOL,                 RTGETOPT_REQ_NOTHING },
    314     { "--no-force-final-eol",               SCMOPT_NO_FORCE_FINAL_EOL,              RTGETOPT_REQ_NOTHING },
    315     { "--force-trailing-line",              SCMOPT_FORCE_TRAILING_LINE,             RTGETOPT_REQ_NOTHING },
    316     { "--no-force-trailing-line",           SCMOPT_NO_FORCE_TRAILING_LINE,          RTGETOPT_REQ_NOTHING },
    317     { "--strip-trailing-blanks",            SCMOPT_STRIP_TRAILING_BLANKS,           RTGETOPT_REQ_NOTHING },
    318     { "--no-strip-trailing-blanks",         SCMOPT_NO_STRIP_TRAILING_BLANKS,        RTGETOPT_REQ_NOTHING },
    319     { "--strip-trailing-lines",             SCMOPT_STRIP_TRAILING_LINES,            RTGETOPT_REQ_NOTHING },
    320     { "--strip-no-trailing-lines",          SCMOPT_NO_STRIP_TRAILING_LINES,         RTGETOPT_REQ_NOTHING },
    321     { "--only-svn-dirs",                    SCMOPT_ONLY_SVN_DIRS,                   RTGETOPT_REQ_NOTHING },
    322     { "--not-only-svn-dirs",                SCMOPT_NOT_ONLY_SVN_DIRS,               RTGETOPT_REQ_NOTHING },
    323     { "--only-svn-files",                   SCMOPT_ONLY_SVN_FILES,                  RTGETOPT_REQ_NOTHING },
    324     { "--not-only-svn-files",               SCMOPT_NOT_ONLY_SVN_FILES,              RTGETOPT_REQ_NOTHING },
    325     { "--set-svn-eol",                      SCMOPT_SET_SVN_EOL,                     RTGETOPT_REQ_NOTHING },
    326     { "--dont-set-svn-eol",                 SCMOPT_DONT_SET_SVN_EOL,                RTGETOPT_REQ_NOTHING },
    327     { "--set-svn-executable",               SCMOPT_SET_SVN_EXECUTABLE,              RTGETOPT_REQ_NOTHING },
    328     { "--dont-set-svn-executable",          SCMOPT_DONT_SET_SVN_EXECUTABLE,         RTGETOPT_REQ_NOTHING },
    329     { "--set-svn-keywords",                 SCMOPT_SET_SVN_KEYWORDS,                RTGETOPT_REQ_NOTHING },
    330     { "--dont-set-svn-keywords",            SCMOPT_DONT_SET_SVN_KEYWORDS,           RTGETOPT_REQ_NOTHING },
    331     { "--tab-size",                         SCMOPT_TAB_SIZE,                        RTGETOPT_REQ_UINT8   },
    332     { "--filter-out-dirs",                  SCMOPT_FILTER_OUT_DIRS,                 RTGETOPT_REQ_STRING  },
    333     { "--filter-files",                     SCMOPT_FILTER_FILES,                    RTGETOPT_REQ_STRING  },
    334     { "--filter-out-files",                 SCMOPT_FILTER_OUT_FILES,                RTGETOPT_REQ_STRING  },
    335 };
    336 
    337 /** Consider files matching the following patterns (base names only). */
    338 static const char  *g_pszFileFilter         = NULL;
    339 
    340 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
    341 {
    342     rewrite_SvnNoExecutable,
    343     rewrite_Makefile_kup
    344 };
    345 
    346 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
    347 {
    348     rewrite_ForceNativeEol,
    349     rewrite_StripTrailingBlanks,
    350     rewrite_AdjustTrailingLines,
    351     rewrite_SvnNoExecutable,
    352     rewrite_SvnKeywords,
    353     rewrite_Makefile_kmk
    354 };
    355 
    356 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
    357 {
    358     rewrite_ForceNativeEol,
    359     rewrite_ExpandTabs,
    360     rewrite_StripTrailingBlanks,
    361     rewrite_AdjustTrailingLines,
    362     rewrite_SvnNoExecutable,
    363     rewrite_SvnKeywords,
    364     rewrite_C_and_CPP
    365 };
    366 
    367 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
    368 {
    369     rewrite_ForceNativeEol,
    370     rewrite_ExpandTabs,
    371     rewrite_StripTrailingBlanks,
    372     rewrite_AdjustTrailingLines,
    373     rewrite_SvnNoExecutable,
    374     rewrite_C_and_CPP
    375 };
    376 
    377 static PFNSCMREWRITER const g_aRewritersFor_RC[] =
    378 {
    379     rewrite_ForceNativeEol,
    380     rewrite_ExpandTabs,
    381     rewrite_StripTrailingBlanks,
    382     rewrite_AdjustTrailingLines,
    383     rewrite_SvnNoExecutable,
    384     rewrite_SvnKeywords
    385 };
    386 
    387 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
    388 {
    389     rewrite_ForceLF,
    390     rewrite_ExpandTabs,
    391     rewrite_StripTrailingBlanks
    392 };
    393 
    394 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
    395 {
    396     rewrite_ForceCRLF,
    397     rewrite_ExpandTabs,
    398     rewrite_StripTrailingBlanks
    399 };
    400 
    401 static SCMCFGENTRY const g_aConfigs[] =
    402 {
    403     { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
    404     { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
    405     { RT_ELEMENTS(g_aRewritersFor_C_and_CPP),    &g_aRewritersFor_C_and_CPP[0],    "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
    406     { RT_ELEMENTS(g_aRewritersFor_H_and_HPP),    &g_aRewritersFor_H_and_HPP[0],    "*.h|*.hpp" },
    407     { RT_ELEMENTS(g_aRewritersFor_RC),           &g_aRewritersFor_RC[0],           "*.rc" },
    408     { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
    409     { RT_ELEMENTS(g_aRewritersFor_BatchFiles),   &g_aRewritersFor_BatchFiles[0],   "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
    410 };
    411 
    412 
    413 
    414 /* -=-=-=-=-=- settings -=-=-=-=-=- */
    415 
    416 
    417 /**
    418  * Init a settings structure with settings from @a pSrc.
    419  *
    420  * @returns IPRT status code
    421  * @param   pSettings           The settings.
    422  * @param   pSrc                The source settings.
    423  */
    424 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
    425 {
    426     *pSettings = *pSrc;
    427 
    428     int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
    429     if (RT_SUCCESS(rc))
    430     {
    431         rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
    432         if (RT_SUCCESS(rc))
    433         {
    434             rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
    435             if (RT_SUCCESS(rc))
    436                 return VINF_SUCCESS;
    437 
    438             RTStrFree(pSettings->pszFilterOutFiles);
    439         }
    440         RTStrFree(pSettings->pszFilterFiles);
    441     }
    442 
    443     pSettings->pszFilterFiles = NULL;
    444     pSettings->pszFilterOutFiles = NULL;
    445     pSettings->pszFilterOutDirs = NULL;
    446     return rc;
    447 }
    448 
    449 /**
    450  * Init a settings structure.
    451  *
    452  * @returns IPRT status code
    453  * @param   pSettings           The settings.
    454  */
    455 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
    456 {
    457     return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
    458 }
    459 
    460 /**
    461  * Deletes the settings, i.e. free any dynamically allocated content.
    462  *
    463  * @param   pSettings           The settings.
    464  */
    465 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
    466 {
    467     if (pSettings)
    468     {
    469         Assert(pSettings->cchTab != ~(unsigned)0);
    470         pSettings->cchTab = ~(unsigned)0;
    471 
    472         RTStrFree(pSettings->pszFilterFiles);
    473         pSettings->pszFilterFiles = NULL;
    474 
    475         RTStrFree(pSettings->pszFilterOutFiles);
    476         pSettings->pszFilterOutFiles = NULL;
    477 
    478         RTStrFree(pSettings->pszFilterOutDirs);
    479         pSettings->pszFilterOutDirs = NULL;
    480     }
    481 }
    482 
    483 
    484 /**
    485  * Processes a RTGetOpt result.
    486  *
    487  * @retval  VINF_SUCCESS if handled.
    488  * @retval  VERR_OUT_OF_RANGE if the option value was out of range.
    489  * @retval  VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
    490  *
    491  * @param   pSettings           The settings to change.
    492  * @param   rc                  The RTGetOpt return value.
    493  * @param   pValueUnion         The RTGetOpt value union.
    494  */
    495 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
    496 {
    497     switch (rc)
    498     {
    499         case SCMOPT_CONVERT_EOL:
    500             pSettings->fConvertEol = true;
    501             return VINF_SUCCESS;
    502         case SCMOPT_NO_CONVERT_EOL:
    503             pSettings->fConvertEol = false;
    504             return VINF_SUCCESS;
    505 
    506         case SCMOPT_CONVERT_TABS:
    507             pSettings->fConvertTabs = true;
    508             return VINF_SUCCESS;
    509         case SCMOPT_NO_CONVERT_TABS:
    510             pSettings->fConvertTabs = false;
    511             return VINF_SUCCESS;
    512 
    513         case SCMOPT_FORCE_FINAL_EOL:
    514             pSettings->fForceFinalEol = true;
    515             return VINF_SUCCESS;
    516         case SCMOPT_NO_FORCE_FINAL_EOL:
    517             pSettings->fForceFinalEol = false;
    518             return VINF_SUCCESS;
    519 
    520         case SCMOPT_FORCE_TRAILING_LINE:
    521             pSettings->fForceTrailingLine = true;
    522             return VINF_SUCCESS;
    523         case SCMOPT_NO_FORCE_TRAILING_LINE:
    524             pSettings->fForceTrailingLine = false;
    525             return VINF_SUCCESS;
    526 
    527         case SCMOPT_STRIP_TRAILING_BLANKS:
    528             pSettings->fStripTrailingBlanks = true;
    529             return VINF_SUCCESS;
    530         case SCMOPT_NO_STRIP_TRAILING_BLANKS:
    531             pSettings->fStripTrailingBlanks = false;
    532             return VINF_SUCCESS;
    533 
    534         case SCMOPT_STRIP_TRAILING_LINES:
    535             pSettings->fStripTrailingLines = true;
    536             return VINF_SUCCESS;
    537         case SCMOPT_NO_STRIP_TRAILING_LINES:
    538             pSettings->fStripTrailingLines = false;
    539             return VINF_SUCCESS;
    540 
    541         case SCMOPT_ONLY_SVN_DIRS:
    542             pSettings->fOnlySvnDirs = true;
    543             return VINF_SUCCESS;
    544         case SCMOPT_NOT_ONLY_SVN_DIRS:
    545             pSettings->fOnlySvnDirs = false;
    546             return VINF_SUCCESS;
    547 
    548         case SCMOPT_ONLY_SVN_FILES:
    549             pSettings->fOnlySvnFiles = true;
    550             return VINF_SUCCESS;
    551         case SCMOPT_NOT_ONLY_SVN_FILES:
    552             pSettings->fOnlySvnFiles = false;
    553             return VINF_SUCCESS;
    554 
    555         case SCMOPT_SET_SVN_EOL:
    556             pSettings->fSetSvnEol = true;
    557             return VINF_SUCCESS;
    558         case SCMOPT_DONT_SET_SVN_EOL:
    559             pSettings->fSetSvnEol = false;
    560             return VINF_SUCCESS;
    561 
    562         case SCMOPT_SET_SVN_EXECUTABLE:
    563             pSettings->fSetSvnExecutable = true;
    564             return VINF_SUCCESS;
    565         case SCMOPT_DONT_SET_SVN_EXECUTABLE:
    566             pSettings->fSetSvnExecutable = false;
    567             return VINF_SUCCESS;
    568 
    569         case SCMOPT_SET_SVN_KEYWORDS:
    570             pSettings->fSetSvnKeywords = true;
    571             return VINF_SUCCESS;
    572         case SCMOPT_DONT_SET_SVN_KEYWORDS:
    573             pSettings->fSetSvnKeywords = false;
    574             return VINF_SUCCESS;
    575 
    576         case SCMOPT_TAB_SIZE:
    577             if (   pValueUnion->u8 < 1
    578                 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
    579             {
    580                 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
    581                            pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
    582                 return VERR_OUT_OF_RANGE;
    583             }
    584             pSettings->cchTab = pValueUnion->u8;
    585             return VINF_SUCCESS;
    586 
    587         case SCMOPT_FILTER_OUT_DIRS:
    588         case SCMOPT_FILTER_FILES:
    589         case SCMOPT_FILTER_OUT_FILES:
    590         {
    591             char **ppsz = NULL;
    592             switch (rc)
    593             {
    594                 case SCMOPT_FILTER_OUT_DIRS:    ppsz = &pSettings->pszFilterOutDirs; break;
    595                 case SCMOPT_FILTER_FILES:       ppsz = &pSettings->pszFilterFiles; break;
    596                 case SCMOPT_FILTER_OUT_FILES:   ppsz = &pSettings->pszFilterOutFiles; break;
    597             }
    598 
    599             /*
    600              * An empty string zaps the current list.
    601              */
    602             if (!*pValueUnion->psz)
    603                 return RTStrATruncate(ppsz, 0);
    604 
    605             /*
    606              * Non-empty strings are appended to the pattern list.
    607              *
    608              * Strip leading and trailing pattern separators before attempting
    609              * to append it.  If it's just separators, don't do anything.
    610              */
    611             const char *pszSrc = pValueUnion->psz;
    612             while (*pszSrc == '|')
    613                 pszSrc++;
    614             size_t cchSrc = strlen(pszSrc);
    615             while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
    616                 cchSrc--;
    617             if (!cchSrc)
    618                 return VINF_SUCCESS;
    619 
    620             return RTStrAAppendExN(ppsz, 2,
    621                                    "|", *ppsz && **ppsz ? 1 : 0,
    622                                    pszSrc, cchSrc);
    623         }
    624 
    625         default:
    626             return VERR_GETOPT_UNKNOWN_OPTION;
    627     }
    628 }
    629 
    630 /**
    631  * Parses an option string.
    632  *
    633  * @returns IPRT status code.
    634  * @param   pBase               The base settings structure to apply the options
    635  *                              to.
    636  * @param   pszOptions          The options to parse.
    637  */
    638 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
    639 {
    640     int    cArgs;
    641     char **papszArgs;
    642     int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
    643     if (RT_SUCCESS(rc))
    644     {
    645         RTGETOPTUNION   ValueUnion;
    646         RTGETOPTSTATE   GetOptState;
    647         rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
    648         if (RT_SUCCESS(rc))
    649         {
    650             while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    651             {
    652                 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
    653                 if (RT_FAILURE(rc))
    654                     break;
    655             }
    656         }
    657         RTGetOptArgvFree(papszArgs);
    658     }
    659 
    660     return rc;
    661 }
    662 
    663 /**
    664  * Parses an unterminated option string.
    665  *
    666  * @returns IPRT status code.
    667  * @param   pBase               The base settings structure to apply the options
    668  *                              to.
    669  * @param   pchLine             The line.
    670  * @param   cchLine             The line length.
    671  */
    672 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
    673 {
    674     char *pszLine = RTStrDupN(pchLine, cchLine);
    675     if (!pszLine)
    676         return VERR_NO_MEMORY;
    677     int rc = scmSettingsBaseParseString(pBase, pszLine);
    678     RTStrFree(pszLine);
    679     return rc;
    680 }
    681 
    682 /**
    683  * Verifies the options string.
    684  *
    685  * @returns IPRT status code.
    686  * @param   pszOptions          The options to verify .
    687  */
    688 static int scmSettingsBaseVerifyString(const char *pszOptions)
    689 {
    690     SCMSETTINGSBASE Base;
    691     int rc = scmSettingsBaseInit(&Base);
    692     if (RT_SUCCESS(rc))
    693     {
    694         rc = scmSettingsBaseParseString(&Base, pszOptions);
    695         scmSettingsBaseDelete(&Base);
    696     }
    697     return rc;
    698 }
    699 
    700 /**
    701  * Loads settings found in editor and SCM settings directives within the
    702  * document (@a pStream).
    703  *
    704  * @returns IPRT status code.
    705  * @param   pBase               The settings base to load settings into.
    706  * @param   pStream             The stream to scan for settings directives.
    707  */
    708 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
    709 {
    710     /** @todo Editor and SCM settings directives in documents.  */
    711     return VINF_SUCCESS;
    712 }
    713 
    714 /**
    715  * Creates a new settings file struct, cloning @a pSettings.
    716  *
    717  * @returns IPRT status code.
    718  * @param   ppSettings          Where to return the new struct.
    719  * @param   pSettingsBase       The settings to inherit from.
    720  */
    721 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
    722 {
    723     PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
    724     if (!pSettings)
    725         return VERR_NO_MEMORY;
    726     int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
    727     if (RT_SUCCESS(rc))
    728     {
    729         pSettings->pDown   = NULL;
    730         pSettings->pUp     = NULL;
    731         pSettings->paPairs = NULL;
    732         pSettings->cPairs  = 0;
    733         *ppSettings = pSettings;
    734         return VINF_SUCCESS;
    735     }
    736     RTMemFree(pSettings);
    737     return rc;
    738 }
    739 
    740 /**
    741  * Destroys a settings structure.
    742  *
    743  * @param   pSettings           The settings structure to destroy.  NULL is OK.
    744  */
    745 static void scmSettingsDestroy(PSCMSETTINGS pSettings)
    746 {
    747     if (pSettings)
    748     {
    749         scmSettingsBaseDelete(&pSettings->Base);
    750         for (size_t i = 0; i < pSettings->cPairs; i++)
    751         {
    752             RTStrFree(pSettings->paPairs[i].pszPattern);
    753             RTStrFree(pSettings->paPairs[i].pszOptions);
    754             pSettings->paPairs[i].pszPattern = NULL;
    755             pSettings->paPairs[i].pszOptions = NULL;
    756         }
    757         RTMemFree(pSettings->paPairs);
    758         pSettings->paPairs = NULL;
    759         RTMemFree(pSettings);
    760     }
    761 }
    762 
    763 /**
    764  * Adds a pattern/options pair to the settings structure.
    765  *
    766  * @returns IPRT status code.
    767  * @param   pSettings           The settings.
    768  * @param   pchLine             The line containing the unparsed pair.
    769  * @param   cchLine             The length of the line.
    770  */
    771 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
    772 {
    773     /*
    774      * Split the string.
    775      */
    776     const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
    777     if (!pchOptions)
    778         return VERR_INVALID_PARAMETER;
    779     size_t cchPattern = pchOptions - pchLine;
    780     size_t cchOptions = cchLine - cchPattern - 1;
    781     pchOptions++;
    782 
    783     /* strip spaces everywhere */
    784     while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
    785         cchPattern--;
    786     while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
    787         cchPattern--, pchLine++;
    788 
    789     while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
    790         cchOptions--;
    791     while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
    792         cchOptions--, pchOptions++;
    793 
    794     /* Quietly ignore empty patterns and empty options. */
    795     if (!cchOptions || !cchPattern)
    796         return VINF_SUCCESS;
    797 
    798     /*
    799      * Add the pair and verify the option string.
    800      */
    801     uint32_t iPair = pSettings->cPairs;
    802     if ((iPair % 32) == 0)
    803     {
    804         void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
    805         if (!pvNew)
    806             return VERR_NO_MEMORY;
    807         pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
    808     }
    809 
    810     pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
    811     pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
    812     int rc;
    813     if (   pSettings->paPairs[iPair].pszPattern
    814         && pSettings->paPairs[iPair].pszOptions)
    815         rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
    816     else
    817         rc = VERR_NO_MEMORY;
    818     if (RT_SUCCESS(rc))
    819         pSettings->cPairs = iPair + 1;
    820     else
    821     {
    822         RTStrFree(pSettings->paPairs[iPair].pszPattern);
    823         RTStrFree(pSettings->paPairs[iPair].pszOptions);
    824     }
    825     return rc;
    826 }
    827 
    828 /**
    829  * Loads in the settings from @a pszFilename.
    830  *
    831  * @returns IPRT status code.
    832  * @param   pSettings           Where to load the settings file.
    833  * @param   pszFilename         The file to load.
    834  */
    835 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
    836 {
    837     SCMSTREAM Stream;
    838     int rc = ScmStreamInitForReading(&Stream, pszFilename);
    839     if (RT_FAILURE(rc))
    840     {
    841         RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
    842         return rc;
    843     }
    844 
    845     SCMEOL      enmEol;
    846     const char *pchLine;
    847     size_t      cchLine;
    848     while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    849     {
    850         /* Ignore leading spaces. */
    851         while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
    852             pchLine++, cchLine--;
    853 
    854         /* Ignore empty lines and comment lines. */
    855         if (cchLine < 1 || *pchLine == '#')
    856             continue;
    857 
    858         /* What kind of line is it? */
    859         const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
    860         if (pchColon)
    861             rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
    862         else
    863             rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
    864         if (RT_FAILURE(rc))
    865         {
    866             RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
    867             break;
    868         }
    869     }
    870 
    871     if (RT_SUCCESS(rc))
    872     {
    873         rc = ScmStreamGetStatus(&Stream);
    874         if (RT_FAILURE(rc))
    875             RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
    876     }
    877 
    878     ScmStreamDelete(&Stream);
    879     return rc;
    880 }
    881 
    882 /**
    883  * Parse the specified settings file creating a new settings struct from it.
    884  *
    885  * @returns IPRT status code
    886  * @param   ppSettings          Where to return the new settings.
    887  * @param   pszFilename         The file to parse.
    888  * @param   pSettingsBase       The base settings we inherit from.
    889  */
    890 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
    891 {
    892     PSCMSETTINGS pSettings;
    893     int rc = scmSettingsCreate(&pSettings, pSettingsBase);
    894     if (RT_SUCCESS(rc))
    895     {
    896         rc = scmSettingsLoadFile(pSettings, pszFilename);
    897         if (RT_SUCCESS(rc))
    898         {
    899             *ppSettings = pSettings;
    900             return VINF_SUCCESS;
    901         }
    902 
    903         scmSettingsDestroy(pSettings);
    904     }
    905     *ppSettings = NULL;
    906     return rc;
    907 }
    908 
    909 
    910 /**
    911  * Create an initial settings structure when starting processing a new file or
    912  * directory.
    913  *
    914  * This will look for .scm-settings files from the root and down to the
    915  * specified directory, combining them into the returned settings structure.
    916  *
    917  * @returns IPRT status code.
    918  * @param   ppSettings          Where to return the pointer to the top stack
    919  *                              object.
    920  * @param   pBaseSettings       The base settings we inherit from (globals
    921  *                              typically).
    922  * @param   pszPath             The absolute path to the new directory or file.
    923  */
    924 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
    925 {
    926     *ppSettings = NULL;                 /* try shut up gcc. */
    927 
    928     /*
    929      * We'll be working with a stack copy of the path.
    930      */
    931     char    szFile[RTPATH_MAX];
    932     size_t  cchDir = strlen(pszPath);
    933     if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
    934         return VERR_FILENAME_TOO_LONG;
    935 
    936     /*
    937      * Create the bottom-most settings.
    938      */
    939     PSCMSETTINGS pSettings;
    940     int rc = scmSettingsCreate(&pSettings, pBaseSettings);
    941     if (RT_FAILURE(rc))
    942         return rc;
    943 
    944     /*
    945      * Enumerate the path components from the root and down. Load any setting
    946      * files we find.
    947      */
    948     size_t cComponents = RTPathCountComponents(pszPath);
    949     for (size_t i = 1; i <= cComponents; i++)
    950     {
    951         rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
    952         if (RT_SUCCESS(rc))
    953             rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
    954         if (RT_FAILURE(rc))
    955             break;
    956         if (RTFileExists(szFile))
    957         {
    958             rc = scmSettingsLoadFile(pSettings, szFile);
    959             if (RT_FAILURE(rc))
    960                 break;
    961         }
    962     }
    963 
    964     if (RT_SUCCESS(rc))
    965         *ppSettings = pSettings;
    966     else
    967         scmSettingsDestroy(pSettings);
    968     return rc;
    969 }
    970 
    971 /**
    972  * Pushes a new settings set onto the stack.
    973  *
    974  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    975  *                              element.  This will be used as input and output.
    976  * @param   pSettings           The settings to push onto the stack.
    977  */
    978 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
    979 {
    980     PSCMSETTINGS pOld = *ppSettingsStack;
    981     pSettings->pDown  = pOld;
    982     pSettings->pUp    = NULL;
    983     if (pOld)
    984         pOld->pUp = pSettings;
    985     *ppSettingsStack = pSettings;
    986 }
    987 
    988 /**
    989  * Pushes the settings of the specified directory onto the stack.
    990  *
    991  * We will load any .scm-settings in the directory.  A stack entry is added even
    992  * if no settings file was found.
    993  *
    994  * @returns IPRT status code.
    995  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    996  *                              element.  This will be used as input and output.
    997  * @param   pszDir              The directory to do this for.
    998  */
    999 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
    1000 {
    1001     char szFile[RTPATH_MAX];
    1002     int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
    1003     if (RT_SUCCESS(rc))
    1004     {
    1005         PSCMSETTINGS pSettings;
    1006         rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
    1007         if (RT_SUCCESS(rc))
    1008         {
    1009             if (RTFileExists(szFile))
    1010                 rc = scmSettingsLoadFile(pSettings, szFile);
    1011             if (RT_SUCCESS(rc))
    1012             {
    1013                 scmSettingsStackPush(ppSettingsStack, pSettings);
    1014                 return VINF_SUCCESS;
    1015             }
    1016 
    1017             scmSettingsDestroy(pSettings);
    1018         }
    1019     }
    1020     return rc;
    1021 }
    1022 
    1023 
    1024 /**
    1025  * Pops a settings set off the stack.
    1026  *
    1027  * @returns The popped setttings.
    1028  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1029  *                              element.  This will be used as input and output.
    1030  */
    1031 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
    1032 {
    1033     PSCMSETTINGS pRet = *ppSettingsStack;
    1034     PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
    1035     *ppSettingsStack = pNew;
    1036     if (pNew)
    1037         pNew->pUp    = NULL;
    1038     if (pRet)
    1039     {
    1040         pRet->pUp    = NULL;
    1041         pRet->pDown  = NULL;
    1042     }
    1043     return pRet;
    1044 }
    1045 
    1046 /**
    1047  * Pops and destroys the top entry of the stack.
    1048  *
    1049  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1050  *                              element.  This will be used as input and output.
    1051  */
    1052 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
    1053 {
    1054     scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
    1055 }
    1056 
    1057 /**
    1058  * Constructs the base settings for the specified file name.
    1059  *
    1060  * @returns IPRT status code.
    1061  * @param   pSettingsStack      The top element on the settings stack.
    1062  * @param   pszFilename         The file name.
    1063  * @param   pszBasename         The base name (pointer within @a pszFilename).
    1064  * @param   cchBasename         The length of the base name.  (For passing to
    1065  *                              RTStrSimplePatternMultiMatch.)
    1066  * @param   pBase               Base settings to initialize.
    1067  */
    1068 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
    1069                                         const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
    1070 {
    1071     int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
    1072     if (RT_SUCCESS(rc))
    1073     {
    1074         /* find the bottom entry in the stack. */
    1075         PCSCMSETTINGS pCur = pSettingsStack;
    1076         while (pCur->pDown)
    1077             pCur = pCur->pDown;
    1078 
    1079         /* Work our way up thru the stack and look for matching pairs. */
    1080         while (pCur)
    1081         {
    1082             size_t const cPairs = pCur->cPairs;
    1083             if (cPairs)
    1084             {
    1085                 for (size_t i = 0; i < cPairs; i++)
    1086                     if (   RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1087                                                         pszBasename,  cchBasename, NULL)
    1088                         || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1089                                                         pszFilename,  RTSTR_MAX, NULL))
    1090                     {
    1091                         rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
    1092                         if (RT_FAILURE(rc))
    1093                             break;
    1094                     }
    1095                 if (RT_FAILURE(rc))
    1096                     break;
    1097             }
    1098 
    1099             /* advance */
    1100             pCur = pCur->pUp;
    1101         }
    1102     }
    1103     if (RT_FAILURE(rc))
    1104         scmSettingsBaseDelete(pBase);
    1105     return rc;
    1106 }
    1107 
    1108 
    1109 /* -=-=-=-=-=- misc -=-=-=-=-=- */
    1110 
    1111 
    1112 /**
    1113  * Prints a verbose message if the level is high enough.
    1114  *
    1115  * @param   pState              The rewrite state.  Optional.
    1116  * @param   iLevel              The required verbosity level.
    1117  * @param   pszFormat           The message format string.  Can be NULL if we
    1118  *                              only want to trigger the per file message.
    1119  * @param   ...                 Format arguments.
    1120  */
    1121 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
    1122 {
    1123     if (iLevel <= g_iVerbosity)
    1124     {
    1125         if (pState && !pState->fFirst)
    1126         {
    1127             RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
    1128             pState->fFirst = true;
    1129         }
    1130         if (pszFormat)
    1131         {
    1132             RTPrintf(pState
    1133                      ? "%s: info:   "
    1134                      : "%s: info: ",
    1135                      g_szProgName);
    1136             va_list va;
    1137             va_start(va, pszFormat);
    1138             RTPrintfV(pszFormat, va);
    1139             va_end(va);
    1140         }
    1141     }
    1142 }
    1143 
    1144 
    1145 /* -=-=-=-=-=- subversion -=-=-=-=-=- */
    1146 
    1147 #define SCM_WITHOUT_LIBSVN
    1148 
    1149 #ifdef SCM_WITHOUT_LIBSVN
    1150 
    1151 /**
    1152  * Callback that is call for each path to search.
    1153  */
    1154 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    1155 {
    1156     char   *pszDst = (char *)pvUser1;
    1157     size_t  cchDst = (size_t)pvUser2;
    1158     if (cchDst > cchPath)
    1159     {
    1160         memcpy(pszDst, pchPath, cchPath);
    1161         pszDst[cchPath] = '\0';
    1162 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1163         int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
    1164 #else
    1165         int rc = RTPathAppend(pszDst, cchDst, "svn");
    1166 #endif
    1167         if (   RT_SUCCESS(rc)
    1168             && RTFileExists(pszDst))
    1169             return VINF_SUCCESS;
    1170     }
    1171     return VERR_TRY_AGAIN;
    1172 }
    1173 
    1174 
    1175 /**
    1176  * Finds the svn binary.
    1177  *
    1178  * @param   pszPath             Where to store it.  Worst case, we'll return
    1179  *                              "svn" here.
    1180  * @param   cchPath             The size of the buffer pointed to by @a pszPath.
    1181  */
    1182 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
    1183 {
    1184     /** @todo code page fun... */
    1185     Assert(cchPath >= sizeof("svn"));
    1186 #ifdef RT_OS_WINDOWS
    1187     const char *pszEnvVar = RTEnvGet("Path");
    1188 #else
    1189     const char *pszEnvVar = RTEnvGet("PATH");
    1190 #endif
    1191     if (pszPath)
    1192     {
    1193 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1194         int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1195 #else
    1196         int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1197 #endif
    1198         if (RT_SUCCESS(rc))
    1199             return;
    1200     }
    1201     strcpy(pszPath, "svn");
    1202 }
    1203 
    1204 
    1205 /**
    1206  * Construct a dot svn filename for the file being rewritten.
    1207  *
    1208  * @returns IPRT status code.
    1209  * @param   pState              The rewrite state (for the name).
    1210  * @param   pszDir              The directory, including ".svn/".
    1211  * @param   pszSuff             The filename suffix.
    1212  * @param   pszDst              The output buffer.  RTPATH_MAX in size.
    1213  */
    1214 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
    1215 {
    1216     strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
    1217     RTPathStripFilename(pszDst);
    1218 
    1219     int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
    1220     if (RT_SUCCESS(rc))
    1221     {
    1222         rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
    1223         if (RT_SUCCESS(rc))
    1224         {
    1225             size_t cchDst  = strlen(pszDst);
    1226             size_t cchSuff = strlen(pszSuff);
    1227             if (cchDst + cchSuff < RTPATH_MAX)
    1228             {
    1229                 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
    1230                 return VINF_SUCCESS;
    1231             }
    1232             else
    1233                 rc = VERR_BUFFER_OVERFLOW;
    1234         }
    1235     }
    1236     return rc;
    1237 }
    1238 
    1239 /**
    1240  * Interprets the specified string as decimal numbers.
    1241  *
    1242  * @returns true if parsed successfully, false if not.
    1243  * @param   pch                 The string (not terminated).
    1244  * @param   cch                 The string length.
    1245  * @param   pu                  Where to return the value.
    1246  */
    1247 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
    1248 {
    1249     size_t u = 0;
    1250     while (cch-- > 0)
    1251     {
    1252         char ch = *pch++;
    1253         if (ch < '0' || ch > '9')
    1254             return false;
    1255         u *= 10;
    1256         u += ch - '0';
    1257     }
    1258     *pu = u;
    1259     return true;
    1260 }
    1261 
    1262 #endif /* SCM_WITHOUT_LIBSVN */
    1263 
    1264 /**
    1265  * Checks if the file we're operating on is part of a SVN working copy.
    1266  *
    1267  * @returns true if it is, false if it isn't or we cannot tell.
    1268  * @param   pState              The rewrite state to work on.
    1269  */
    1270 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
    1271 {
    1272 #ifdef SCM_WITHOUT_LIBSVN
    1273     /*
    1274      * Hack: check if the .svn/text-base/<file>.svn-base file exists.
    1275      */
    1276     char szPath[RTPATH_MAX];
    1277     int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
    1278     if (RT_SUCCESS(rc))
    1279         return RTFileExists(szPath);
    1280 
    1281 #else
    1282     NOREF(pState);
    1283 #endif
    1284     return false;
    1285 }
    1286 
    1287 /**
    1288  * Queries the value of an SVN property.
    1289  *
    1290  * This will automatically adjust for scheduled changes.
    1291  *
    1292  * @returns IPRT status code.
    1293  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1294  * @retval  VERR_NOT_FOUND if the property wasn't found.
    1295  * @param   pState              The rewrite state to work on.
    1296  * @param   pszName             The property name.
    1297  * @param   ppszValue           Where to return the property value.  Free this
    1298  *                              using RTStrFree.  Optional.
    1299  */
    1300 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
    1301 {
    1302     /*
    1303      * Look it up in the scheduled changes.
    1304      */
    1305     uint32_t i = pState->cSvnPropChanges;
    1306     while (i-- > 0)
    1307         if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
    1308         {
    1309             const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1310             if (!pszValue)
    1311                 return VERR_NOT_FOUND;
    1312             if (ppszValue)
    1313                 return RTStrDupEx(ppszValue, pszValue);
    1314             return VINF_SUCCESS;
    1315         }
    1316 
    1317 #ifdef SCM_WITHOUT_LIBSVN
    1318     /*
    1319      * Hack: Read the .svn/props/<file>.svn-work file exists.
    1320      */
    1321     char szPath[RTPATH_MAX];
    1322     int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
    1323     if (RT_SUCCESS(rc) && !RTFileExists(szPath))
    1324         rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
    1325     if (RT_SUCCESS(rc))
    1326     {
    1327         SCMSTREAM Stream;
    1328         rc = ScmStreamInitForReading(&Stream, szPath);
    1329         if (RT_SUCCESS(rc))
    1330         {
    1331             /*
    1332              * The current format is K len\n<name>\nV len\n<value>\n" ... END.
    1333              */
    1334             rc = VERR_NOT_FOUND;
    1335             size_t const    cchName = strlen(pszName);
    1336             SCMEOL          enmEol;
    1337             size_t          cchLine;
    1338             const char     *pchLine;
    1339             while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    1340             {
    1341                 /*
    1342                  * Parse the 'K num' / 'END' line.
    1343                  */
    1344                 if (   cchLine == 3
    1345                     && !memcmp(pchLine, "END", 3))
    1346                     break;
    1347                 size_t cchKey;
    1348                 if (   cchLine < 3
    1349                     || pchLine[0] != 'K'
    1350                     || pchLine[1] != ' '
    1351                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
    1352                     || cchKey == 0
    1353                     || cchKey > 4096)
    1354                 {
    1355                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1356                     rc = VERR_PARSE_ERROR;
    1357                     break;
    1358                 }
    1359 
    1360                 /*
    1361                  * Match the key and skip to the value line.  Don't bother with
    1362                  * names containing EOL markers.
    1363                  */
    1364                 size_t const offKey = ScmStreamTell(&Stream);
    1365                 bool fMatch = cchName == cchKey;
    1366                 if (fMatch)
    1367                 {
    1368                     pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1369                     if (!pchLine)
    1370                         break;
    1371                     fMatch = cchLine == cchName
    1372                           && !memcmp(pchLine, pszName, cchName);
    1373                 }
    1374 
    1375                 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
    1376                     break;
    1377                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1378                     break;
    1379 
    1380                 /*
    1381                  * Read and Parse the 'V num' line.
    1382                  */
    1383                 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1384                 if (!pchLine)
    1385                     break;
    1386                 size_t cchValue;
    1387                 if (   cchLine < 3
    1388                     || pchLine[0] != 'V'
    1389                     || pchLine[1] != ' '
    1390                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
    1391                     || cchValue > _1M)
    1392                 {
    1393                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1394                     rc = VERR_PARSE_ERROR;
    1395                     break;
    1396                 }
    1397 
    1398                 /*
    1399                  * If we have a match, allocate a return buffer and read the
    1400                  * value into it.  Otherwise skip this value and continue
    1401                  * searching.
    1402                  */
    1403                 if (fMatch)
    1404                 {
    1405                     if (!ppszValue)
    1406                         rc = VINF_SUCCESS;
    1407                     else
    1408                     {
    1409                         char *pszValue;
    1410                         rc = RTStrAllocEx(&pszValue, cchValue + 1);
    1411                         if (RT_SUCCESS(rc))
    1412                         {
    1413                             rc = ScmStreamRead(&Stream, pszValue, cchValue);
    1414                             if (RT_SUCCESS(rc))
    1415                                 *ppszValue = pszValue;
    1416                             else
    1417                                 RTStrFree(pszValue);
    1418                         }
    1419                     }
    1420                     break;
    1421                 }
    1422 
    1423                 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
    1424                     break;
    1425                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1426                     break;
    1427             }
    1428 
    1429             if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
    1430             {
    1431                 rc = ScmStreamGetStatus(&Stream);
    1432                 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
    1433             }
    1434             ScmStreamDelete(&Stream);
    1435         }
    1436     }
    1437 
    1438     if (rc == VERR_FILE_NOT_FOUND)
    1439         rc = VERR_NOT_FOUND;
    1440     return rc;
    1441 
    1442 #else
    1443     NOREF(pState);
    1444 #endif
    1445     return VERR_NOT_FOUND;
    1446 }
    1447 
    1448 
    1449 /**
    1450  * Schedules the setting of a property.
    1451  *
    1452  * @returns IPRT status code.
    1453  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1454  * @param   pState              The rewrite state to work on.
    1455  * @param   pszName             The name of the property to set.
    1456  * @param   pszValue            The value.  NULL means deleting it.
    1457  */
    1458 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
    1459 {
    1460     /*
    1461      * Update any existing entry first.
    1462      */
    1463     size_t i = pState->cSvnPropChanges;
    1464     while (i-- > 0)
    1465         if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
    1466         {
    1467             if (!pszValue)
    1468             {
    1469                 RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1470                 pState->paSvnPropChanges[i].pszValue = NULL;
    1471             }
    1472             else
    1473             {
    1474                 char *pszCopy;
    1475                 int rc = RTStrDupEx(&pszCopy, pszValue);
    1476                 if (RT_FAILURE(rc))
    1477                     return rc;
    1478                 pState->paSvnPropChanges[i].pszValue = pszCopy;
    1479             }
    1480             return VINF_SUCCESS;
    1481         }
    1482 
    1483     /*
    1484      * Insert a new entry.
    1485      */
    1486     i = pState->cSvnPropChanges;
    1487     if ((i % 32) == 0)
    1488     {
    1489         void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
    1490         if (!pvNew)
    1491             return VERR_NO_MEMORY;
    1492         pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
    1493     }
    1494 
    1495     pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
    1496     pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
    1497     if (   pState->paSvnPropChanges[i].pszName
    1498         && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
    1499         pState->cSvnPropChanges = i + 1;
    1500     else
    1501     {
    1502         RTStrFree(pState->paSvnPropChanges[i].pszName);
    1503         pState->paSvnPropChanges[i].pszName = NULL;
    1504         RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1505         pState->paSvnPropChanges[i].pszValue = NULL;
    1506         return VERR_NO_MEMORY;
    1507     }
    1508     return VINF_SUCCESS;
    1509 }
    1510 
    1511 
    1512 /**
    1513  * Schedules a property deletion.
    1514  *
    1515  * @returns IPRT status code.
    1516  * @param   pState              The rewrite state to work on.
    1517  * @param   pszName             The name of the property to delete.
    1518  */
    1519 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
    1520 {
    1521     return scmSvnSetProperty(pState, pszName, NULL);
    1522 }
    1523 
    1524 
    1525 /**
    1526  * Applies any SVN property changes to the work copy of the file.
    1527  *
    1528  * @returns IPRT status code.
    1529  * @param   pState              The rewrite state which SVN property changes
    1530  *                              should be applied.
    1531  */
    1532 static int scmSvnDisplayChanges(PSCMRWSTATE pState)
    1533 {
    1534     size_t i = pState->cSvnPropChanges;
    1535     while (i-- > 0)
    1536     {
    1537         const char *pszName  = pState->paSvnPropChanges[i].pszName;
    1538         const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1539         if (pszValue)
    1540             ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1541         else
    1542             ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1543     }
    1544 
    1545     return VINF_SUCCESS;
    1546 }
    1547 
    1548 /**
    1549  * Applies any SVN property changes to the work copy of the file.
    1550  *
    1551  * @returns IPRT status code.
    1552  * @param   pState              The rewrite state which SVN property changes
    1553  *                              should be applied.
    1554  */
    1555 static int scmSvnApplyChanges(PSCMRWSTATE pState)
    1556 {
    1557 #ifdef SCM_WITHOUT_LIBSVN
    1558     /*
    1559      * This sucks. We gotta find svn(.exe).
    1560      */
    1561     static char s_szSvnPath[RTPATH_MAX];
    1562     if (s_szSvnPath[0] == '\0')
    1563         scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
    1564 
    1565     /*
    1566      * Iterate thru the changes and apply them by starting the svn client.
    1567      */
    1568     for (size_t i = 0; i <pState->cSvnPropChanges; i++)
    1569     {
    1570         const char *apszArgv[6];
    1571         apszArgv[0] = s_szSvnPath;
    1572         apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
    1573         apszArgv[2] = pState->paSvnPropChanges[i].pszName;
    1574         int iArg = 3;
    1575         if (pState->paSvnPropChanges[i].pszValue)
    1576             apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
    1577         apszArgv[iArg++] = pState->pszFilename;
    1578         apszArgv[iArg++] = NULL;
    1579         ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
    1580                    apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
    1581 
    1582         RTPROCESS pid;
    1583         int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
    1584         if (RT_SUCCESS(rc))
    1585         {
    1586             RTPROCSTATUS Status;
    1587             rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
    1588             if (    RT_SUCCESS(rc)
    1589                 &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
    1590                      || Status.iStatus != 0) )
    1591             {
    1592                 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
    1593                            pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
    1594                            Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
    1595                            : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
    1596                            : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
    1597                            : "abducted by alien",
    1598                            Status.iStatus);
    1599                 return VERR_GENERAL_FAILURE;
    1600             }
    1601         }
    1602         if (RT_FAILURE(rc))
    1603         {
    1604             RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
    1605                        pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
    1606             return rc;
    1607         }
    1608     }
    1609 
    1610     return VINF_SUCCESS;
    1611 #else
    1612     return VERR_NOT_IMPLEMENTED;
    1613 #endif
    1614 }
    1615 
    1616 
    1617 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */
     37#include "scm.h"
     38
    161839
    161940
     
    162647 * @param   pSettings           The settings.
    162748 */
    1628 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     49bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    162950{
    163051    if (!pSettings->fStripTrailingBlanks)
     
    166586 * @param   pSettings           The settings.
    166687 */
    1667 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     88bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    166889{
    166990    if (!pSettings->fConvertTabs)
     
    1752173    /* Check svn:eol-style if appropriate */
    1753174    if (   pSettings->fSetSvnEol
    1754         && scmSvnIsInWorkingCopy(pState))
     175        && ScmSvnIsInWorkingCopy(pState))
    1755176    {
    1756177        char *pszEol;
    1757         int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
     178        int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    1758179        if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    1759180            || rc == VERR_NOT_FOUND)
     
    1763184            else
    1764185                ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    1765             int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
     186            int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    1766187            if (RT_FAILURE(rc2))
    1767                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
     188                RTMsgError("ScmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
    1768189        }
    1769190        if (RT_SUCCESS(rc))
     
    1783204 * @param   pSettings           The settings.
    1784205 */
    1785 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     206bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1786207{
    1787208#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
     
    1800221 * @param   pSettings           The settings.
    1801222 */
    1802 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     223bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1803224{
    1804225    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
     
    1813234 * @param   pSettings           The settings.
    1814235 */
    1815 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     236bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1816237{
    1817238    return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
     
    1829250 * @remarks ASSUMES trailing white space has been removed already.
    1830251 */
    1831 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     252bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1832253{
    1833254    if (   !pSettings->fStripTrailingLines
     
    1897318 * @param   pSettings           The settings.
    1898319 */
    1899 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     320bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1900321{
    1901322    if (   !pSettings->fSetSvnExecutable
    1902         || !scmSvnIsInWorkingCopy(pState))
    1903         return false;
    1904 
    1905     int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
     323        || !ScmSvnIsInWorkingCopy(pState))
     324        return false;
     325
     326    int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
    1906327    if (RT_SUCCESS(rc))
    1907328    {
    1908329        ScmVerbose(pState, 2, " * removing svn:executable\n");
    1909         rc = scmSvnDelProperty(pState, "svn:executable");
     330        rc = ScmSvnDelProperty(pState, "svn:executable");
    1910331        if (RT_FAILURE(rc))
    1911             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
     332            RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    1912333    }
    1913334    return false;
     
    1923344 * @param   pSettings           The settings.
    1924345 */
    1925 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     346bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1926347{
    1927348    if (   !pSettings->fSetSvnKeywords
    1928         || !scmSvnIsInWorkingCopy(pState))
     349        || !ScmSvnIsInWorkingCopy(pState))
    1929350        return false;
    1930351
    1931352    char *pszKeywords;
    1932     int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
     353    int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    1933354    if (    RT_SUCCESS(rc)
    1934355        && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
     
    1944365        {
    1945366            ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    1946             rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
     367            rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    1947368            if (RT_FAILURE(rc))
    1948                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
     369                RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    1949370        }
    1950371        else
     
    1955376    {
    1956377        ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    1957         rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
     378        rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    1958379        if (RT_FAILURE(rc))
    1959             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
     380            RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    1960381    }
    1961382    else if (RT_SUCCESS(rc))
     
    1973394 * @param   pSettings           The settings.
    1974395 */
    1975 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     396bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1976397{
    1977398    /* These files should be zero bytes. */
     
    1996417 *      - line continuation slashes should only be preceded by one space.
    1997418 */
    1998 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     419bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    1999420{
    2000421    return false;
     
    2032453 *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    2033454 */
    2034 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
     455bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2035456{
    2036457
     
    2038459}
    2039460
    2040 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
    2041 
    2042 /**
    2043  * Processes a file.
    2044  *
    2045  * @returns IPRT status code.
    2046  * @param   pState              The rewriter state.
    2047  * @param   pszFilename         The file name.
    2048  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2049  * @param   cchBasename         The length of the base name.  (For passing to
    2050  *                              RTStrSimplePatternMultiMatch.)
    2051  * @param   pBaseSettings       The base settings to use.  It's OK to modify
    2052  *                              these.
    2053  */
    2054 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2055                                PSCMSETTINGSBASE pBaseSettings)
    2056 {
    2057     /*
    2058      * Do the file level filtering.
    2059      */
    2060     if (   pBaseSettings->pszFilterFiles
    2061         && *pBaseSettings->pszFilterFiles
    2062         && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2063     {
    2064         ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
    2065         return VINF_SUCCESS;
    2066     }
    2067     if (   pBaseSettings->pszFilterOutFiles
    2068         && *pBaseSettings->pszFilterOutFiles
    2069         && (   RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
    2070             || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
    2071     {
    2072         ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
    2073         return VINF_SUCCESS;
    2074     }
    2075     if (   pBaseSettings->fOnlySvnFiles
    2076         && !scmSvnIsInWorkingCopy(pState))
    2077     {
    2078         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
    2079         return VINF_SUCCESS;
    2080     }
    2081 
    2082     /*
    2083      * Try find a matching rewrite config for this filename.
    2084      */
    2085     PCSCMCFGENTRY pCfg = NULL;
    2086     for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
    2087         if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2088         {
    2089             pCfg = &g_aConfigs[iCfg];
    2090             break;
    2091         }
    2092     if (!pCfg)
    2093     {
    2094         ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
    2095         return VINF_SUCCESS;
    2096     }
    2097     ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
    2098 
    2099     /*
    2100      * Create an input stream from the file and check that it's text.
    2101      */
    2102     SCMSTREAM Stream1;
    2103     int rc = ScmStreamInitForReading(&Stream1, pszFilename);
    2104     if (RT_FAILURE(rc))
    2105     {
    2106         RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
    2107         return rc;
    2108     }
    2109     if (ScmStreamIsText(&Stream1))
    2110     {
    2111         ScmVerbose(pState, 3, NULL);
    2112 
    2113         /*
    2114          * Gather SCM and editor settings from the stream.
    2115          */
    2116         rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
    2117         if (RT_SUCCESS(rc))
    2118         {
    2119             ScmStreamRewindForReading(&Stream1);
    2120 
    2121             /*
    2122              * Create two more streams for output and push the text thru all the
    2123              * rewriters, switching the two streams around when something is
    2124              * actually rewritten.  Stream1 remains unchanged.
    2125              */
    2126             SCMSTREAM Stream2;
    2127             rc = ScmStreamInitForWriting(&Stream2, &Stream1);
    2128             if (RT_SUCCESS(rc))
    2129             {
    2130                 SCMSTREAM Stream3;
    2131                 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
    2132                 if (RT_SUCCESS(rc))
    2133                 {
    2134                     bool        fModified = false;
    2135                     PSCMSTREAM  pIn       = &Stream1;
    2136                     PSCMSTREAM  pOut      = &Stream2;
    2137                     for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
    2138                     {
    2139                         bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
    2140                         if (fRc)
    2141                         {
    2142                             PSCMSTREAM pTmp = pOut;
    2143                             pOut = pIn == &Stream1 ? &Stream3 : pIn;
    2144                             pIn  = pTmp;
    2145                             fModified = true;
    2146                         }
    2147                         ScmStreamRewindForReading(pIn);
    2148                         ScmStreamRewindForWriting(pOut);
    2149                     }
    2150 
    2151                     rc = ScmStreamGetStatus(&Stream1);
    2152                     if (RT_SUCCESS(rc))
    2153                         rc = ScmStreamGetStatus(&Stream2);
    2154                     if (RT_SUCCESS(rc))
    2155                         rc = ScmStreamGetStatus(&Stream3);
    2156                     if (RT_SUCCESS(rc))
    2157                     {
    2158                         /*
    2159                          * If rewritten, write it back to disk.
    2160                          */
    2161                         if (fModified)
    2162                         {
    2163                             if (!g_fDryRun)
    2164                             {
    2165                                 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2166                                 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
    2167                                 if (RT_FAILURE(rc))
    2168                                     RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
    2169                             }
    2170                             else
    2171                             {
    2172                                 ScmVerbose(pState, 1, NULL);
    2173                                 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
    2174                                                g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
    2175                                 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2176                             }
    2177                         }
    2178 
    2179                         /*
    2180                          * If pending SVN property changes, apply them.
    2181                          */
    2182                         if (pState->cSvnPropChanges && RT_SUCCESS(rc))
    2183                         {
    2184                             if (!g_fDryRun)
    2185                             {
    2186                                 rc = scmSvnApplyChanges(pState);
    2187                                 if (RT_FAILURE(rc))
    2188                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
    2189                             }
    2190                             else
    2191                                 scmSvnDisplayChanges(pState);
    2192                         }
    2193 
    2194                         if (!fModified && !pState->cSvnPropChanges)
    2195                             ScmVerbose(pState, 3, "no change\n", pszFilename);
    2196                     }
    2197                     else
    2198                         RTMsgError("%s: stream error %Rrc\n", pszFilename);
    2199                     ScmStreamDelete(&Stream3);
    2200                 }
    2201                 else
    2202                     RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2203                 ScmStreamDelete(&Stream2);
    2204             }
    2205             else
    2206                 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2207         }
    2208         else
    2209             RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
    2210     }
    2211     else
    2212         ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
    2213     ScmStreamDelete(&Stream1);
    2214 
    2215     return rc;
    2216 }
    2217 
    2218 /**
    2219  * Processes a file.
    2220  *
    2221  * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
    2222  * directory recursion method.
    2223  *
    2224  * @returns IPRT status code.
    2225  * @param   pszFilename         The file name.
    2226  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2227  * @param   cchBasename         The length of the base name.  (For passing to
    2228  *                              RTStrSimplePatternMultiMatch.)
    2229  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2230  */
    2231 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2232                           PSCMSETTINGS pSettingsStack)
    2233 {
    2234     SCMSETTINGSBASE Base;
    2235     int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
    2236     if (RT_SUCCESS(rc))
    2237     {
    2238         SCMRWSTATE State;
    2239         State.fFirst           = false;
    2240         State.pszFilename      = pszFilename;
    2241         State.cSvnPropChanges  = 0;
    2242         State.paSvnPropChanges = NULL;
    2243 
    2244         rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
    2245 
    2246         size_t i = State.cSvnPropChanges;
    2247         while (i-- > 0)
    2248         {
    2249             RTStrFree(State.paSvnPropChanges[i].pszName);
    2250             RTStrFree(State.paSvnPropChanges[i].pszValue);
    2251         }
    2252         RTMemFree(State.paSvnPropChanges);
    2253 
    2254         scmSettingsBaseDelete(&Base);
    2255     }
    2256     return rc;
    2257 }
    2258 
    2259 
    2260 /**
    2261  * Tries to correct RTDIRENTRY_UNKNOWN.
    2262  *
    2263  * @returns Corrected type.
    2264  * @param   pszPath             The path to the object in question.
    2265  */
    2266 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
    2267 {
    2268     RTFSOBJINFO Info;
    2269     int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
    2270     if (RT_FAILURE(rc))
    2271         return RTDIRENTRYTYPE_UNKNOWN;
    2272     if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
    2273         return RTDIRENTRYTYPE_DIRECTORY;
    2274     if (RTFS_IS_FILE(Info.Attr.fMode))
    2275         return RTDIRENTRYTYPE_FILE;
    2276     return RTDIRENTRYTYPE_UNKNOWN;
    2277 }
    2278 
    2279 /**
    2280  * Recurse into a sub-directory and process all the files and directories.
    2281  *
    2282  * @returns IPRT status code.
    2283  * @param   pszBuf              Path buffer containing the directory path on
    2284  *                              entry.  This ends with a dot.  This is passed
    2285  *                              along when recursing in order to save stack space
    2286  *                              and avoid needless copying.
    2287  * @param   cchDir              Length of our path in pszbuf.
    2288  * @param   pEntry              Directory entry buffer.  This is also passed
    2289  *                              along when recursing to save stack space.
    2290  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2291  * @param   iRecursion          The recursion depth.  This is used to restrict
    2292  *                              the recursions.
    2293  */
    2294 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
    2295                                       PSCMSETTINGS pSettingsStack, unsigned iRecursion)
    2296 {
    2297     int rc;
    2298     Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
    2299 
    2300     /*
    2301      * Make sure we stop somewhere.
    2302      */
    2303     if (iRecursion > 128)
    2304     {
    2305         RTMsgError("recursion too deep: %d\n", iRecursion);
    2306         return VINF_SUCCESS; /* ignore */
    2307     }
    2308 
    2309     /*
    2310      * Check if it's excluded by --only-svn-dir.
    2311      */
    2312     if (pSettingsStack->Base.fOnlySvnDirs)
    2313     {
    2314         rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
    2315         if (RT_FAILURE(rc))
    2316         {
    2317             RTMsgError("RTPathAppend: %Rrc\n", rc);
    2318             return rc;
    2319         }
    2320         if (!RTDirExists(pszBuf))
    2321             return VINF_SUCCESS;
    2322 
    2323         Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
    2324         pszBuf[cchDir]     = '\0';
    2325         pszBuf[cchDir - 1] = '.';
    2326     }
    2327 
    2328     /*
    2329      * Try open and read the directory.
    2330      */
    2331     PRTDIR pDir;
    2332     rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
    2333     if (RT_FAILURE(rc))
    2334     {
    2335         RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
    2336         return rc;
    2337     }
    2338     for (;;)
    2339     {
    2340         /* Read the next entry. */
    2341         rc = RTDirRead(pDir, pEntry, NULL);
    2342         if (RT_FAILURE(rc))
    2343         {
    2344             if (rc == VERR_NO_MORE_FILES)
    2345                 rc = VINF_SUCCESS;
    2346             else
    2347                 RTMsgError("RTDirRead -> %Rrc\n", rc);
    2348             break;
    2349         }
    2350 
    2351         /* Skip '.' and '..'. */
    2352         if (    pEntry->szName[0] == '.'
    2353             &&  (   pEntry->cbName == 1
    2354                  || (   pEntry->cbName == 2
    2355                      && pEntry->szName[1] == '.')))
    2356             continue;
    2357 
    2358         /* Enter it into the buffer so we've got a full name to work
    2359            with when needed. */
    2360         if (pEntry->cbName + cchDir >= RTPATH_MAX)
    2361         {
    2362             RTMsgError("Skipping too long entry: %s", pEntry->szName);
    2363             continue;
    2364         }
    2365         memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
    2366 
    2367         /* Figure the type. */
    2368         RTDIRENTRYTYPE enmType = pEntry->enmType;
    2369         if (enmType == RTDIRENTRYTYPE_UNKNOWN)
    2370             enmType = scmFigureUnknownType(pszBuf);
    2371 
    2372         /* Process the file or directory, skip the rest. */
    2373         if (enmType == RTDIRENTRYTYPE_FILE)
    2374             rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
    2375         else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
    2376         {
    2377             /* Append the dot for the benefit of the pattern matching. */
    2378             if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
    2379             {
    2380                 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
    2381                 continue;
    2382             }
    2383             memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
    2384             size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
    2385 
    2386             if (   !pSettingsStack->Base.pszFilterOutDirs
    2387                 || !*pSettingsStack->Base.pszFilterOutDirs
    2388                 || (   !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2389                                                      pEntry->szName, pEntry->cbName, NULL)
    2390                     && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2391                                                      pszBuf, cchSubDir, NULL)
    2392                    )
    2393                )
    2394             {
    2395                 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
    2396                 if (RT_SUCCESS(rc))
    2397                 {
    2398                     rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
    2399                     scmSettingsStackPopAndDestroy(&pSettingsStack);
    2400                 }
    2401             }
    2402         }
    2403         if (RT_FAILURE(rc))
    2404             break;
    2405     }
    2406     RTDirClose(pDir);
    2407     return rc;
    2408 
    2409 }
    2410 
    2411 /**
    2412  * Process a directory tree.
    2413  *
    2414  * @returns IPRT status code.
    2415  * @param   pszDir              The directory to start with.  This is pointer to
    2416  *                              a RTPATH_MAX sized buffer.
    2417  */
    2418 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
    2419 {
    2420     /*
    2421      * Setup the recursion.
    2422      */
    2423     int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
    2424     if (RT_SUCCESS(rc))
    2425     {
    2426         RTDIRENTRY Entry;
    2427         rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
    2428     }
    2429     else
    2430         RTMsgError("RTPathAppend: %Rrc\n", rc);
    2431     return rc;
    2432 }
    2433 
    2434 
    2435 /**
    2436  * Processes a file or directory specified as an command line argument.
    2437  *
    2438  * @returns IPRT status code
    2439  * @param   pszSomething        What we found in the command line arguments.
    2440  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2441  */
    2442 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
    2443 {
    2444     char szBuf[RTPATH_MAX];
    2445     int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
    2446     if (RT_SUCCESS(rc))
    2447     {
    2448         RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
    2449 
    2450         PSCMSETTINGS pSettings;
    2451         rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
    2452         if (RT_SUCCESS(rc))
    2453         {
    2454             scmSettingsStackPush(&pSettingsStack, pSettings);
    2455 
    2456             if (RTFileExists(szBuf))
    2457             {
    2458                 const char *pszBasename = RTPathFilename(szBuf);
    2459                 if (pszBasename)
    2460                 {
    2461                     size_t cchBasename = strlen(pszBasename);
    2462                     rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
    2463                 }
    2464                 else
    2465                 {
    2466                     RTMsgError("RTPathFilename: NULL\n");
    2467                     rc = VERR_IS_A_DIRECTORY;
    2468                 }
    2469             }
    2470             else
    2471                 rc = scmProcessDirTree(szBuf, pSettingsStack);
    2472 
    2473             PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
    2474             Assert(pPopped == pSettings);
    2475             scmSettingsDestroy(pSettings);
    2476         }
    2477         else
    2478             RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
    2479     }
    2480     else
    2481         RTMsgError("RTPathAbs: %Rrc\n", rc);
    2482     return rc;
    2483 }
    2484 
    2485 int main(int argc, char **argv)
    2486 {
    2487     int rc = RTR3InitExe(argc, &argv, 0);
    2488     if (RT_FAILURE(rc))
    2489         return 1;
    2490 
    2491     /*
    2492      * Init the settings.
    2493      */
    2494     PSCMSETTINGS pSettings;
    2495     rc = scmSettingsCreate(&pSettings, &g_Defaults);
    2496     if (RT_FAILURE(rc))
    2497     {
    2498         RTMsgError("scmSettingsCreate: %Rrc\n", rc);
    2499         return 1;
    2500     }
    2501 
    2502     /*
    2503      * Parse arguments and process input in order (because this is the only
    2504      * thing that works at the moment).
    2505      */
    2506     static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
    2507     {
    2508         { "--dry-run",                          'd',                                    RTGETOPT_REQ_NOTHING },
    2509         { "--real-run",                         'D',                                    RTGETOPT_REQ_NOTHING },
    2510         { "--file-filter",                      'f',                                    RTGETOPT_REQ_STRING  },
    2511         { "--quiet",                            'q',                                    RTGETOPT_REQ_NOTHING },
    2512         { "--verbose",                          'v',                                    RTGETOPT_REQ_NOTHING },
    2513         { "--diff-ignore-eol",                  SCMOPT_DIFF_IGNORE_EOL,                 RTGETOPT_REQ_NOTHING },
    2514         { "--diff-no-ignore-eol",               SCMOPT_DIFF_NO_IGNORE_EOL,              RTGETOPT_REQ_NOTHING },
    2515         { "--diff-ignore-space",                SCMOPT_DIFF_IGNORE_SPACE,               RTGETOPT_REQ_NOTHING },
    2516         { "--diff-no-ignore-space",             SCMOPT_DIFF_NO_IGNORE_SPACE,            RTGETOPT_REQ_NOTHING },
    2517         { "--diff-ignore-leading-space",        SCMOPT_DIFF_IGNORE_LEADING_SPACE,       RTGETOPT_REQ_NOTHING },
    2518         { "--diff-no-ignore-leading-space",     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,    RTGETOPT_REQ_NOTHING },
    2519         { "--diff-ignore-trailing-space",       SCMOPT_DIFF_IGNORE_TRAILING_SPACE,      RTGETOPT_REQ_NOTHING },
    2520         { "--diff-no-ignore-trailing-space",    SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,   RTGETOPT_REQ_NOTHING },
    2521         { "--diff-special-chars",               SCMOPT_DIFF_SPECIAL_CHARS,              RTGETOPT_REQ_NOTHING },
    2522         { "--diff-no-special-chars",            SCMOPT_DIFF_NO_SPECIAL_CHARS,           RTGETOPT_REQ_NOTHING },
    2523     };
    2524     memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
    2525 
    2526     RTGETOPTUNION   ValueUnion;
    2527     RTGETOPTSTATE   GetOptState;
    2528     rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
    2529     AssertReleaseRCReturn(rc, 1);
    2530     size_t          cProcessed = 0;
    2531 
    2532     while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    2533     {
    2534         switch (rc)
    2535         {
    2536             case 'd':
    2537                 g_fDryRun = true;
    2538                 break;
    2539             case 'D':
    2540                 g_fDryRun = false;
    2541                 break;
    2542 
    2543             case 'f':
    2544                 g_pszFileFilter = ValueUnion.psz;
    2545                 break;
    2546 
    2547             case 'h':
    2548                 RTPrintf("VirtualBox Source Code Massager\n"
    2549                          "\n"
    2550                          "Usage: %s [options] <files & dirs>\n"
    2551                          "\n"
    2552                          "Options:\n", g_szProgName);
    2553                 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
    2554                 {
    2555                     bool fAdvanceTwo = false;
    2556                     if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
    2557                     {
    2558                         fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
    2559                                    && (   strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
    2560                                        || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
    2561                                        || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
    2562                                       );
    2563                         if (fAdvanceTwo)
    2564                             RTPrintf("  %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
    2565                         else
    2566                             RTPrintf("  %s\n", s_aOpts[i].pszLong);
    2567                     }
    2568                     else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
    2569                         RTPrintf("  %s string\n", s_aOpts[i].pszLong);
    2570                     else
    2571                         RTPrintf("  %s value\n", s_aOpts[i].pszLong);
    2572                     switch (s_aOpts[i].iShort)
    2573                     {
    2574                         case SCMOPT_CONVERT_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertEol); break;
    2575                         case SCMOPT_CONVERT_TABS:           RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertTabs); break;
    2576                         case SCMOPT_FORCE_FINAL_EOL:        RTPrintf("      Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
    2577                         case SCMOPT_FORCE_TRAILING_LINE:    RTPrintf("      Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
    2578                         case SCMOPT_STRIP_TRAILING_BLANKS:  RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
    2579                         case SCMOPT_STRIP_TRAILING_LINES:   RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
    2580                         case SCMOPT_ONLY_SVN_DIRS:          RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
    2581                         case SCMOPT_ONLY_SVN_FILES:         RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
    2582                         case SCMOPT_SET_SVN_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
    2583                         case SCMOPT_SET_SVN_EXECUTABLE:     RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
    2584                         case SCMOPT_SET_SVN_KEYWORDS:       RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
    2585                         case SCMOPT_TAB_SIZE:               RTPrintf("      Default: %u\n", g_Defaults.cchTab); break;
    2586                         case SCMOPT_FILTER_OUT_DIRS:        RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutDirs); break;
    2587                         case SCMOPT_FILTER_FILES:           RTPrintf("      Default: %s\n", g_Defaults.pszFilterFiles); break;
    2588                         case SCMOPT_FILTER_OUT_FILES:       RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutFiles); break;
    2589                     }
    2590                     i += fAdvanceTwo;
    2591                 }
    2592                 return 1;
    2593 
    2594             case 'q':
    2595                 g_iVerbosity = 0;
    2596                 break;
    2597 
    2598             case 'v':
    2599                 g_iVerbosity++;
    2600                 break;
    2601 
    2602             case 'V':
    2603             {
    2604                 /* The following is assuming that svn does it's job here. */
    2605                 static const char s_szRev[] = "$Revision$";
    2606                 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
    2607                 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
    2608                 return 0;
    2609             }
    2610 
    2611             case SCMOPT_DIFF_IGNORE_EOL:
    2612                 g_fDiffIgnoreEol = true;
    2613                 break;
    2614             case SCMOPT_DIFF_NO_IGNORE_EOL:
    2615                 g_fDiffIgnoreEol = false;
    2616                 break;
    2617 
    2618             case SCMOPT_DIFF_IGNORE_SPACE:
    2619                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
    2620                 break;
    2621             case SCMOPT_DIFF_NO_IGNORE_SPACE:
    2622                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
    2623                 break;
    2624 
    2625             case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
    2626                 g_fDiffIgnoreLeadingWS = true;
    2627                 break;
    2628             case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
    2629                 g_fDiffIgnoreLeadingWS = false;
    2630                 break;
    2631 
    2632             case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
    2633                 g_fDiffIgnoreTrailingWS = true;
    2634                 break;
    2635             case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
    2636                 g_fDiffIgnoreTrailingWS = false;
    2637                 break;
    2638 
    2639             case SCMOPT_DIFF_SPECIAL_CHARS:
    2640                 g_fDiffSpecialChars = true;
    2641                 break;
    2642             case SCMOPT_DIFF_NO_SPECIAL_CHARS:
    2643                 g_fDiffSpecialChars = false;
    2644                 break;
    2645 
    2646             case VINF_GETOPT_NOT_OPTION:
    2647             {
    2648                 if (!g_fDryRun)
    2649                 {
    2650                     if (!cProcessed)
    2651                     {
    2652                         RTPrintf("%s: Warning! This program will make changes to your source files and\n"
    2653                                  "%s:          there is a slight risk that bugs or a full disk may cause\n"
    2654                                  "%s:          LOSS OF DATA.   So, please make sure you have checked in\n"
    2655                                  "%s:          all your changes already.  If you didn't, then don't blame\n"
    2656                                  "%s:          anyone for not warning you!\n"
    2657                                  "%s:\n"
    2658                                  "%s:          Press any key to continue...\n",
    2659                                  g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
    2660                                  g_szProgName, g_szProgName);
    2661                         RTStrmGetCh(g_pStdIn);
    2662                     }
    2663                     cProcessed++;
    2664                 }
    2665                 rc = scmProcessSomething(ValueUnion.psz, pSettings);
    2666                 if (RT_FAILURE(rc))
    2667                     return rc;
    2668                 break;
    2669             }
    2670 
    2671             default:
    2672             {
    2673                 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
    2674                 if (RT_SUCCESS(rc2))
    2675                     break;
    2676                 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
    2677                     return 2;
    2678                 return RTGetOptPrintError(rc, &ValueUnion);
    2679             }
    2680         }
    2681     }
    2682 
    2683     scmSettingsDestroy(pSettings);
    2684     return 0;
    2685 }
    2686 
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette