VirtualBox

Changeset 40530 in vbox for trunk


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

scm: Splitting out the diff code while I'm at it.

Location:
trunk/src/bldprogs
Files:
3 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/bldprogs/Makefile.kmk

    r40528 r40530  
    3838 scm_SOURCES = \
    3939        scm.cpp \
    40         scmstream.cpp
     40        scmstream.cpp \
     41        scmdiff.cpp
    4142 scm_LIBS = \
    4243        $(LIB_RUNTIME)
  • trunk/src/bldprogs/scm.cpp

    r40528 r40530  
    3636
    3737#include "scmstream.h"
     38#include "scmdiff.h"
    3839
    3940
     
    116117typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    117118
    118 
    119 /**
    120  * Diff state.
    121  */
    122 typedef struct SCMDIFFSTATE
    123 {
    124     size_t          cDiffs;
    125     const char     *pszFilename;
    126 
    127     PSCMSTREAM      pLeft;
    128     PSCMSTREAM      pRight;
    129 
    130     /** Whether to ignore end of line markers when diffing. */
    131     bool            fIgnoreEol;
    132     /** Whether to ignore trailing whitespace. */
    133     bool            fIgnoreTrailingWhite;
    134     /** Whether to ignore leading whitespace. */
    135     bool            fIgnoreLeadingWhite;
    136     /** Whether to print special characters in human readable form or not. */
    137     bool            fSpecialChars;
    138     /** The tab size. */
    139     size_t          cchTab;
    140     /** Where to push the diff. */
    141     PRTSTREAM       pDiff;
    142 } SCMDIFFSTATE;
    143 /** Pointer to a diff state. */
    144 typedef SCMDIFFSTATE *PSCMDIFFSTATE;
    145119
    146120/**
     
    437411
    438412
    439 /* -=-=-=-=-=- diff -=-=-=-=-=- */
    440 
    441 
    442 /**
    443  * Prints a range of lines with a prefix.
    444  *
    445  * @param   pState              The diff state.
    446  * @param   chPrefix            The prefix.
    447  * @param   pStream             The stream to get the lines from.
    448  * @param   iLine               The first line.
    449  * @param   cLines              The number of lines.
    450  */
    451 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
    452 {
    453     while (cLines-- > 0)
    454     {
    455         SCMEOL      enmEol;
    456         size_t      cchLine;
    457         const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    458 
    459         RTStrmPutCh(pState->pDiff, chPrefix);
    460         if (pchLine && cchLine)
    461         {
    462             if (!pState->fSpecialChars)
    463                 RTStrmWrite(pState->pDiff, pchLine, cchLine);
    464             else
    465             {
    466                 size_t      offVir   = 0;
    467                 const char *pchStart = pchLine;
    468                 const char *pchTab   = (const char *)memchr(pchLine, '\t', cchLine);
    469                 while (pchTab)
    470                 {
    471                     RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
    472                     offVir += pchTab - pchStart;
    473 
    474                     size_t cchTab = pState->cchTab - offVir % pState->cchTab;
    475                     switch (cchTab)
    476                     {
    477                         case 1: RTStrmPutStr(pState->pDiff, "."); break;
    478                         case 2: RTStrmPutStr(pState->pDiff, ".."); break;
    479                         case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
    480                         case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
    481                         case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
    482                         default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
    483                     }
    484                     offVir += cchTab;
    485 
    486                     /* next */
    487                     pchStart = pchTab + 1;
    488                     pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
    489                 }
    490                 size_t cchLeft = cchLine - (pchStart - pchLine);
    491                 if (cchLeft)
    492                     RTStrmWrite(pState->pDiff, pchStart, cchLeft);
    493             }
    494         }
    495 
    496         if (!pState->fSpecialChars)
    497             RTStrmPutCh(pState->pDiff, '\n');
    498         else if (enmEol == SCMEOL_LF)
    499             RTStrmPutStr(pState->pDiff, "[LF]\n");
    500         else if (enmEol == SCMEOL_CRLF)
    501             RTStrmPutStr(pState->pDiff, "[CRLF]\n");
    502         else
    503             RTStrmPutStr(pState->pDiff, "[NONE]\n");
    504 
    505         iLine++;
    506     }
    507 }
    508 
    509 
    510 /**
    511  * Reports a difference and propels the streams to the lines following the
    512  * resync.
    513  *
    514  *
    515  * @returns New pState->cDiff value (just to return something).
    516  * @param   pState              The diff state.  The cDiffs member will be
    517  *                              incremented.
    518  * @param   cMatches            The resync length.
    519  * @param   iLeft               Where the difference starts on the left side.
    520  * @param   cLeft               How long it is on this side.  ~(size_t)0 is used
    521  *                              to indicate that it goes all the way to the end.
    522  * @param   iRight              Where the difference starts on the right side.
    523  * @param   cRight              How long it is.
    524  */
    525 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
    526                             size_t iLeft, size_t cLeft,
    527                             size_t iRight, size_t cRight)
    528 {
    529     /*
    530      * Adjust the input.
    531      */
    532     if (cLeft == ~(size_t)0)
    533     {
    534         size_t c = ScmStreamCountLines(pState->pLeft);
    535         if (c >= iLeft)
    536             cLeft = c - iLeft;
    537         else
    538         {
    539             iLeft = c;
    540             cLeft = 0;
    541         }
    542     }
    543 
    544     if (cRight == ~(size_t)0)
    545     {
    546         size_t c = ScmStreamCountLines(pState->pRight);
    547         if (c >= iRight)
    548             cRight = c - iRight;
    549         else
    550         {
    551             iRight = c;
    552             cRight = 0;
    553         }
    554     }
    555 
    556     /*
    557      * Print header if it's the first difference
    558      */
    559     if (!pState->cDiffs)
    560         RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
    561 
    562     /*
    563      * Emit the change description.
    564      */
    565     char ch = cLeft == 0
    566             ? 'a'
    567             : cRight == 0
    568             ? 'd'
    569             : 'c';
    570     if (cLeft > 1 && cRight > 1)
    571         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
    572     else if (cLeft > 1)
    573         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n",     iLeft + 1, iLeft + cLeft, ch, iRight + 1);
    574     else if (cRight > 1)
    575         RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n",     iLeft + 1,                ch, iRight + 1, iRight + cRight);
    576     else
    577         RTStrmPrintf(pState->pDiff, "%zu%c%zu\n",         iLeft + 1,                ch, iRight + 1);
    578 
    579     /*
    580      * And the lines.
    581      */
    582     if (cLeft)
    583         scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
    584     if (cLeft && cRight)
    585         RTStrmPrintf(pState->pDiff, "---\n");
    586     if (cRight)
    587         scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
    588 
    589     /*
    590      * Reposition the streams (safely ignores return value).
    591      */
    592     ScmStreamSeekByLine(pState->pLeft,  iLeft  + cLeft  + cMatches);
    593     ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
    594 
    595     pState->cDiffs++;
    596     return pState->cDiffs;
    597 }
    598 
    599 /**
    600  * Helper for scmDiffCompare that takes care of trailing spaces and stuff
    601  * like that.
    602  */
    603 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
    604                                const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    605                                const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    606 {
    607     if (pState->fIgnoreTrailingWhite)
    608     {
    609         while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
    610             cchLeft--;
    611         while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
    612             cchRight--;
    613     }
    614 
    615     if (pState->fIgnoreLeadingWhite)
    616     {
    617         while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
    618             pchLeft++, cchLeft--;
    619         while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
    620             pchRight++, cchRight--;
    621     }
    622 
    623     if (   cchLeft != cchRight
    624         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    625         || memcmp(pchLeft, pchRight, cchLeft))
    626         return false;
    627     return true;
    628 }
    629 
    630 /**
    631  * Compare two lines.
    632  *
    633  * @returns true if the are equal, false if not.
    634  */
    635 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
    636                                 const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    637                                 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    638 {
    639     if (   cchLeft != cchRight
    640         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    641         || memcmp(pchLeft, pchRight, cchLeft))
    642     {
    643         if (   pState->fIgnoreTrailingWhite
    644             || pState->fIgnoreTrailingWhite)
    645             return scmDiffCompareSlow(pState,
    646                                       pchLeft, cchLeft, enmEolLeft,
    647                                       pchRight, cchRight, enmEolRight);
    648         return false;
    649     }
    650     return true;
    651 }
    652 
    653 /**
    654  * Compares two sets of lines from the two files.
    655  *
    656  * @returns true if they matches, false if they don't.
    657  * @param   pState              The diff state.
    658  * @param   iLeft               Where to start in the left stream.
    659  * @param   iRight              Where to start in the right stream.
    660  * @param   cLines              How many lines to compare.
    661  */
    662 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
    663 {
    664     for (size_t iLine = 0; iLine < cLines; iLine++)
    665     {
    666         SCMEOL      enmEolLeft;
    667         size_t      cchLeft;
    668         const char *pchLeft  = ScmStreamGetLineByNo(pState->pLeft,  iLeft + iLine,  &cchLeft,  &enmEolLeft);
    669 
    670         SCMEOL      enmEolRight;
    671         size_t      cchRight;
    672         const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
    673 
    674         if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    675             return false;
    676     }
    677     return true;
    678 }
    679 
    680 
    681 /**
    682  * Resynchronize the two streams and reports the difference.
    683  *
    684  * Upon return, the streams will be positioned after the block of @a cMatches
    685  * lines where it resynchronized them.
    686  *
    687  * @returns pState->cDiffs (just so we can use it in a return statement).
    688  * @param   pState              The state.
    689  * @param   cMatches            The number of lines that needs to match for the
    690  *                              stream to be considered synchronized again.
    691  */
    692 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
    693 {
    694     size_t const iStartLeft  = ScmStreamTellLine(pState->pLeft)  - 1;
    695     size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
    696     Assert(cMatches > 0);
    697 
    698     /*
    699      * Compare each new line from each of the streams will all the preceding
    700      * ones, including iStartLeft/Right.
    701      */
    702     for (size_t iRange = 1; ; iRange++)
    703     {
    704         /*
    705          * Get the next line in the left stream and compare it against all the
    706          * preceding lines on the right side.
    707          */
    708         SCMEOL      enmEol;
    709         size_t      cchLine;
    710         const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
    711         if (!pchLine)
    712             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    713 
    714         for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
    715         {
    716             SCMEOL      enmEolRight;
    717             size_t      cchRight;
    718             const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
    719                                                         &cchRight, &enmEolRight);
    720             if (   scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
    721                 && scmDiffCompareLines(pState,
    722                                        iStartLeft  + iRange + 1 - cMatches,
    723                                        iStartRight + iRight + 1 - cMatches,
    724                                        cMatches - 1)
    725                )
    726                 return scmDiffReport(pState, cMatches,
    727                                      iStartLeft,  iRange + 1 - cMatches,
    728                                      iStartRight, iRight + 1 - cMatches);
    729         }
    730 
    731         /*
    732          * Get the next line in the right stream and compare it against all the
    733          * lines on the right side.
    734          */
    735         pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
    736         if (!pchLine)
    737             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    738 
    739         for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
    740         {
    741             SCMEOL      enmEolLeft;
    742             size_t      cchLeft;
    743             const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
    744                                                        &cchLeft, &enmEolLeft);
    745             if (    scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
    746                 && scmDiffCompareLines(pState,
    747                                        iStartLeft  + iLeft  + 1 - cMatches,
    748                                        iStartRight + iRange + 1 - cMatches,
    749                                        cMatches - 1)
    750                )
    751                 return scmDiffReport(pState, cMatches,
    752                                      iStartLeft,  iLeft  + 1 - cMatches,
    753                                      iStartRight, iRange + 1 - cMatches);
    754         }
    755     }
    756 }
    757 
    758 /**
    759  * Creates a diff of the changes between the streams @a pLeft and @a pRight.
    760  *
    761  * This currently only implements the simplest diff format, so no contexts.
    762  *
    763  * Also, note that we won't detect differences in the final newline of the
    764  * streams.
    765  *
    766  * @returns The number of differences.
    767  * @param   pszFilename         The filename.
    768  * @param   pLeft               The left side stream.
    769  * @param   pRight              The right side stream.
    770  * @param   fIgnoreEol          Whether to ignore end of line markers.
    771  * @param   fIgnoreLeadingWhite Set if leading white space should be ignored.
    772  * @param   fIgnoreTrailingWhite  Set if trailing white space should be ignored.
    773  * @param   fSpecialChars       Whether to print special chars in a human
    774  *                              readable form or not.
    775  * @param   cchTab              The tab size.
    776  * @param   pDiff               Where to write the diff.
    777  */
    778 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
    779                       bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
    780                       size_t cchTab, PRTSTREAM pDiff)
    781 {
    782 #ifdef RT_STRICT
    783     ScmStreamCheckItegrity(pLeft);
    784     ScmStreamCheckItegrity(pRight);
    785 #endif
    786 
    787     /*
    788      * Set up the diff state.
    789      */
    790     SCMDIFFSTATE State;
    791     State.cDiffs                = 0;
    792     State.pszFilename           = pszFilename;
    793     State.pLeft                 = pLeft;
    794     State.pRight                = pRight;
    795     State.fIgnoreEol            = fIgnoreEol;
    796     State.fIgnoreLeadingWhite   = fIgnoreLeadingWhite;
    797     State.fIgnoreTrailingWhite  = fIgnoreTrailingWhite;
    798     State.fSpecialChars         = fSpecialChars;
    799     State.cchTab                = cchTab;
    800     State.pDiff                 = pDiff;
    801 
    802     /*
    803      * Compare them line by line.
    804      */
    805     ScmStreamRewindForReading(pLeft);
    806     ScmStreamRewindForReading(pRight);
    807     const char *pchLeft;
    808     const char *pchRight;
    809 
    810     for (;;)
    811     {
    812         SCMEOL  enmEolLeft;
    813         size_t  cchLeft;
    814         pchLeft  = ScmStreamGetLine(pLeft,  &cchLeft,  &enmEolLeft);
    815 
    816         SCMEOL  enmEolRight;
    817         size_t  cchRight;
    818         pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
    819         if (!pchLeft || !pchRight)
    820             break;
    821 
    822         if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    823             scmDiffSynchronize(&State, 3);
    824     }
    825 
    826     /*
    827      * Deal with any remaining differences.
    828      */
    829     if (pchLeft)
    830         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
    831     else if (pchRight)
    832         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
    833 
    834     /*
    835      * Report any errors.
    836      */
    837     if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
    838         RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
    839     if (RT_FAILURE(ScmStreamGetStatus(pRight)))
    840         RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
    841 
    842     return State.cDiffs;
    843 }
    844 
    845 
    846413
    847414/* -=-=-=-=-=- settings -=-=-=-=-=- */
     415
    848416
    849417/**
  • trunk/src/bldprogs/scmdiff.cpp

    r40528 r40530  
    2121#include <iprt/assert.h>
    2222#include <iprt/ctype.h>
    23 #include <iprt/dir.h>
    24 #include <iprt/env.h>
    25 #include <iprt/file.h>
    26 #include <iprt/err.h>
    27 #include <iprt/getopt.h>
    28 #include <iprt/initterm.h>
    29 #include <iprt/mem.h>
    3023#include <iprt/message.h>
    31 #include <iprt/param.h>
    32 #include <iprt/path.h>
    33 #include <iprt/process.h>
    3424#include <iprt/stream.h>
    3525#include <iprt/string.h>
    3626
    37 #include "scmstream.h"
    38 
    39 
    40 /*******************************************************************************
    41 *   Defined Constants And Macros                                               *
    42 *******************************************************************************/
    43 /** The name of the settings files. */
    44 #define SCM_SETTINGS_FILENAME           ".scm-settings"
    45 
    46 
    47 /*******************************************************************************
    48 *   Structures and Typedefs                                                    *
    49 *******************************************************************************/
    50 /** Pointer to const massager settings. */
    51 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
    52 
    53 /**
    54  * SVN property.
    55  */
    56 typedef struct SCMSVNPROP
    57 {
    58     /** The property. */
    59     char           *pszName;
    60     /** The value.
    61      * When used to record updates, this can be set to NULL to trigger the
    62      * deletion of the property. */
    63     char           *pszValue;
    64 } SCMSVNPROP;
    65 /** Pointer to a SVN property. */
    66 typedef SCMSVNPROP *PSCMSVNPROP;
    67 /** Pointer to a const  SVN property. */
    68 typedef SCMSVNPROP const *PCSCMSVNPROP;
    69 
    70 
    71 /**
    72  * Rewriter state.
    73  */
    74 typedef struct SCMRWSTATE
    75 {
    76     /** The filename.  */
    77     const char     *pszFilename;
    78     /** Set after the printing the first verbose message about a file under
    79      *  rewrite. */
    80     bool            fFirst;
    81     /** The number of SVN property changes. */
    82     size_t          cSvnPropChanges;
    83     /** Pointer to an array of SVN property changes. */
    84     PSCMSVNPROP     paSvnPropChanges;
    85 } SCMRWSTATE;
    86 /** Pointer to the rewriter state. */
    87 typedef SCMRWSTATE *PSCMRWSTATE;
    88 
    89 /**
    90  * A rewriter.
    91  *
    92  * This works like a stream editor, reading @a pIn, modifying it and writing it
    93  * to @a pOut.
    94  *
    95  * @returns true if any changes were made, false if not.
    96  * @param   pIn                 The input stream.
    97  * @param   pOut                The output stream.
    98  * @param   pSettings           The settings.
    99  */
    100 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    101 
    102 
    103 /**
    104  * Configuration entry.
    105  */
    106 typedef struct SCMCFGENTRY
    107 {
    108     /** Number of rewriters. */
    109     size_t          cRewriters;
    110     /** Pointer to an array of rewriters. */
    111     PFNSCMREWRITER const  *papfnRewriter;
    112     /** File pattern (simple).  */
    113     const char     *pszFilePattern;
    114 } SCMCFGENTRY;
    115 typedef SCMCFGENTRY *PSCMCFGENTRY;
    116 typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    117 
    118 
    119 /**
    120  * Diff state.
    121  */
    122 typedef struct SCMDIFFSTATE
    123 {
    124     size_t          cDiffs;
    125     const char     *pszFilename;
    126 
    127     PSCMSTREAM      pLeft;
    128     PSCMSTREAM      pRight;
    129 
    130     /** Whether to ignore end of line markers when diffing. */
    131     bool            fIgnoreEol;
    132     /** Whether to ignore trailing whitespace. */
    133     bool            fIgnoreTrailingWhite;
    134     /** Whether to ignore leading whitespace. */
    135     bool            fIgnoreLeadingWhite;
    136     /** Whether to print special characters in human readable form or not. */
    137     bool            fSpecialChars;
    138     /** The tab size. */
    139     size_t          cchTab;
    140     /** Where to push the diff. */
    141     PRTSTREAM       pDiff;
    142 } SCMDIFFSTATE;
    143 /** Pointer to a diff state. */
    144 typedef SCMDIFFSTATE *PSCMDIFFSTATE;
    145 
    146 /**
    147  * Source Code Massager Settings.
    148  */
    149 typedef struct SCMSETTINGSBASE
    150 {
    151     bool            fConvertEol;
    152     bool            fConvertTabs;
    153     bool            fForceFinalEol;
    154     bool            fForceTrailingLine;
    155     bool            fStripTrailingBlanks;
    156     bool            fStripTrailingLines;
    157     /** Only process files that are part of a SVN working copy. */
    158     bool            fOnlySvnFiles;
    159     /** Only recurse into directories containing an .svn dir.  */
    160     bool            fOnlySvnDirs;
    161     /** Set svn:eol-style if missing or incorrect. */
    162     bool            fSetSvnEol;
    163     /** Set svn:executable according to type (unusually this means deleting it). */
    164     bool            fSetSvnExecutable;
    165     /** Set svn:keyword if completely or partially missing. */
    166     bool            fSetSvnKeywords;
    167     /**  */
    168     unsigned        cchTab;
    169     /** Only consider files matching these patterns.  This is only applied to the
    170      *  base names. */
    171     char           *pszFilterFiles;
    172     /** Filter out files matching the following patterns.  This is applied to base
    173      *  names as well as the absolute paths.  */
    174     char           *pszFilterOutFiles;
    175     /** Filter out directories matching the following patterns.  This is applied
    176      *  to base names as well as the absolute paths.  All absolute paths ends with a
    177      *  slash and dot ("/.").  */
    178     char           *pszFilterOutDirs;
    179 } SCMSETTINGSBASE;
    180 /** Pointer to massager settings. */
    181 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
    182 
    183 /**
    184  * Option identifiers.
    185  *
    186  * @note    The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
    187  *          clear.  So, the option setting a flag (boolean) will have an even
    188  *          number and the one clearing it will have an odd number.
    189  * @note    Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
    190  */
    191 typedef enum SCMOPT
    192 {
    193     SCMOPT_CONVERT_EOL = 10000,
    194     SCMOPT_NO_CONVERT_EOL,
    195     SCMOPT_CONVERT_TABS,
    196     SCMOPT_NO_CONVERT_TABS,
    197     SCMOPT_FORCE_FINAL_EOL,
    198     SCMOPT_NO_FORCE_FINAL_EOL,
    199     SCMOPT_FORCE_TRAILING_LINE,
    200     SCMOPT_NO_FORCE_TRAILING_LINE,
    201     SCMOPT_STRIP_TRAILING_BLANKS,
    202     SCMOPT_NO_STRIP_TRAILING_BLANKS,
    203     SCMOPT_STRIP_TRAILING_LINES,
    204     SCMOPT_NO_STRIP_TRAILING_LINES,
    205     SCMOPT_ONLY_SVN_DIRS,
    206     SCMOPT_NOT_ONLY_SVN_DIRS,
    207     SCMOPT_ONLY_SVN_FILES,
    208     SCMOPT_NOT_ONLY_SVN_FILES,
    209     SCMOPT_SET_SVN_EOL,
    210     SCMOPT_DONT_SET_SVN_EOL,
    211     SCMOPT_SET_SVN_EXECUTABLE,
    212     SCMOPT_DONT_SET_SVN_EXECUTABLE,
    213     SCMOPT_SET_SVN_KEYWORDS,
    214     SCMOPT_DONT_SET_SVN_KEYWORDS,
    215     SCMOPT_TAB_SIZE,
    216     SCMOPT_FILTER_OUT_DIRS,
    217     SCMOPT_FILTER_FILES,
    218     SCMOPT_FILTER_OUT_FILES,
    219     SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
    220     //
    221     SCMOPT_DIFF_IGNORE_EOL,
    222     SCMOPT_DIFF_NO_IGNORE_EOL,
    223     SCMOPT_DIFF_IGNORE_SPACE,
    224     SCMOPT_DIFF_NO_IGNORE_SPACE,
    225     SCMOPT_DIFF_IGNORE_LEADING_SPACE,
    226     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
    227     SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
    228     SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
    229     SCMOPT_DIFF_SPECIAL_CHARS,
    230     SCMOPT_DIFF_NO_SPECIAL_CHARS,
    231     SCMOPT_END
    232 } SCMOPT;
    233 
    234 
    235 /**
    236  * File/dir pattern + options.
    237  */
    238 typedef struct SCMPATRNOPTPAIR
    239 {
    240     char *pszPattern;
    241     char *pszOptions;
    242 } SCMPATRNOPTPAIR;
    243 /** Pointer to a pattern + option pair. */
    244 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
    245 
    246 
    247 /** Pointer to a settings set. */
    248 typedef struct SCMSETTINGS *PSCMSETTINGS;
    249 /**
    250  * Settings set.
    251  *
    252  * This structure is constructed from the command line arguments or any
    253  * .scm-settings file found in a directory we recurse into.  When recursing in
    254  * and out of a directory, we push and pop a settings set for it.
    255  *
    256  * The .scm-settings file has two kinds of setttings, first there are the
    257  * unqualified base settings and then there are the settings which applies to a
    258  * set of files or directories.  The former are lines with command line options.
    259  * For the latter, the options are preceded by a string pattern and a colon.
    260  * The pattern specifies which files (and/or directories) the options applies
    261  * to.
    262  *
    263  * We parse the base options into the Base member and put the others into the
    264  * paPairs array.
    265  */
    266 typedef struct SCMSETTINGS
    267 {
    268     /** Pointer to the setting file below us in the stack. */
    269     PSCMSETTINGS        pDown;
    270     /** Pointer to the setting file above us in the stack. */
    271     PSCMSETTINGS        pUp;
    272     /** File/dir patterns and their options. */
    273     PSCMPATRNOPTPAIR    paPairs;
    274     /** The number of entires in paPairs. */
    275     uint32_t            cPairs;
    276     /** The base settings that was read out of the file. */
    277     SCMSETTINGSBASE     Base;
    278 } SCMSETTINGS;
    279 /** Pointer to a const settings set. */
    280 typedef SCMSETTINGS const *PCSCMSETTINGS;
    281 
    282 
    283 /*******************************************************************************
    284 *   Internal Functions                                                         *
    285 *******************************************************************************/
    286 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    287 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    288 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    289 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    290 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    291 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    292 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    293 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    294 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    295 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    296 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
     27#include "scmdiff.h"
    29728
    29829
     
    30031*   Global Variables                                                           *
    30132*******************************************************************************/
    302 static const char   g_szProgName[]          = "scm";
    303 static const char  *g_pszChangedSuff        = "";
    30433static const char   g_szTabSpaces[16+1]     = "                ";
    305 static bool         g_fDryRun               = true;
    306 static bool         g_fDiffSpecialChars     = true;
    307 static bool         g_fDiffIgnoreEol        = false;
    308 static bool         g_fDiffIgnoreLeadingWS  = false;
    309 static bool         g_fDiffIgnoreTrailingWS = false;
    310 static int          g_iVerbosity            = 2;//99; //0;
    311 
    312 /** The global settings. */
    313 static SCMSETTINGSBASE const g_Defaults =
    314 {
    315     /* .fConvertEol = */            true,
    316     /* .fConvertTabs = */           true,
    317     /* .fForceFinalEol = */         true,
    318     /* .fForceTrailingLine = */     false,
    319     /* .fStripTrailingBlanks = */   true,
    320     /* .fStripTrailingLines = */    true,
    321     /* .fOnlySvnFiles = */          false,
    322     /* .fOnlySvnDirs = */           false,
    323     /* .fSetSvnEol = */             false,
    324     /* .fSetSvnExecutable = */      false,
    325     /* .fSetSvnKeywords = */        false,
    326     /* .cchTab = */                 8,
    327     /* .pszFilterFiles = */         (char *)"",
    328     /* .pszFilterOutFiles = */      (char *)"*.exe|*.com|20*-*-*.log",
    329     /* .pszFilterOutDirs = */       (char *)".svn|.hg|.git|CVS",
    330 };
    331 
    332 /** Option definitions for the base settings. */
    333 static RTGETOPTDEF  g_aScmOpts[] =
    334 {
    335     { "--convert-eol",                      SCMOPT_CONVERT_EOL,                     RTGETOPT_REQ_NOTHING },
    336     { "--no-convert-eol",                   SCMOPT_NO_CONVERT_EOL,                  RTGETOPT_REQ_NOTHING },
    337     { "--convert-tabs",                     SCMOPT_CONVERT_TABS,                    RTGETOPT_REQ_NOTHING },
    338     { "--no-convert-tabs",                  SCMOPT_NO_CONVERT_TABS,                 RTGETOPT_REQ_NOTHING },
    339     { "--force-final-eol",                  SCMOPT_FORCE_FINAL_EOL,                 RTGETOPT_REQ_NOTHING },
    340     { "--no-force-final-eol",               SCMOPT_NO_FORCE_FINAL_EOL,              RTGETOPT_REQ_NOTHING },
    341     { "--force-trailing-line",              SCMOPT_FORCE_TRAILING_LINE,             RTGETOPT_REQ_NOTHING },
    342     { "--no-force-trailing-line",           SCMOPT_NO_FORCE_TRAILING_LINE,          RTGETOPT_REQ_NOTHING },
    343     { "--strip-trailing-blanks",            SCMOPT_STRIP_TRAILING_BLANKS,           RTGETOPT_REQ_NOTHING },
    344     { "--no-strip-trailing-blanks",         SCMOPT_NO_STRIP_TRAILING_BLANKS,        RTGETOPT_REQ_NOTHING },
    345     { "--strip-trailing-lines",             SCMOPT_STRIP_TRAILING_LINES,            RTGETOPT_REQ_NOTHING },
    346     { "--strip-no-trailing-lines",          SCMOPT_NO_STRIP_TRAILING_LINES,         RTGETOPT_REQ_NOTHING },
    347     { "--only-svn-dirs",                    SCMOPT_ONLY_SVN_DIRS,                   RTGETOPT_REQ_NOTHING },
    348     { "--not-only-svn-dirs",                SCMOPT_NOT_ONLY_SVN_DIRS,               RTGETOPT_REQ_NOTHING },
    349     { "--only-svn-files",                   SCMOPT_ONLY_SVN_FILES,                  RTGETOPT_REQ_NOTHING },
    350     { "--not-only-svn-files",               SCMOPT_NOT_ONLY_SVN_FILES,              RTGETOPT_REQ_NOTHING },
    351     { "--set-svn-eol",                      SCMOPT_SET_SVN_EOL,                     RTGETOPT_REQ_NOTHING },
    352     { "--dont-set-svn-eol",                 SCMOPT_DONT_SET_SVN_EOL,                RTGETOPT_REQ_NOTHING },
    353     { "--set-svn-executable",               SCMOPT_SET_SVN_EXECUTABLE,              RTGETOPT_REQ_NOTHING },
    354     { "--dont-set-svn-executable",          SCMOPT_DONT_SET_SVN_EXECUTABLE,         RTGETOPT_REQ_NOTHING },
    355     { "--set-svn-keywords",                 SCMOPT_SET_SVN_KEYWORDS,                RTGETOPT_REQ_NOTHING },
    356     { "--dont-set-svn-keywords",            SCMOPT_DONT_SET_SVN_KEYWORDS,           RTGETOPT_REQ_NOTHING },
    357     { "--tab-size",                         SCMOPT_TAB_SIZE,                        RTGETOPT_REQ_UINT8   },
    358     { "--filter-out-dirs",                  SCMOPT_FILTER_OUT_DIRS,                 RTGETOPT_REQ_STRING  },
    359     { "--filter-files",                     SCMOPT_FILTER_FILES,                    RTGETOPT_REQ_STRING  },
    360     { "--filter-out-files",                 SCMOPT_FILTER_OUT_FILES,                RTGETOPT_REQ_STRING  },
    361 };
    362 
    363 /** Consider files matching the following patterns (base names only). */
    364 static const char  *g_pszFileFilter         = NULL;
    365 
    366 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
    367 {
    368     rewrite_SvnNoExecutable,
    369     rewrite_Makefile_kup
    370 };
    371 
    372 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
    373 {
    374     rewrite_ForceNativeEol,
    375     rewrite_StripTrailingBlanks,
    376     rewrite_AdjustTrailingLines,
    377     rewrite_SvnNoExecutable,
    378     rewrite_SvnKeywords,
    379     rewrite_Makefile_kmk
    380 };
    381 
    382 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
    383 {
    384     rewrite_ForceNativeEol,
    385     rewrite_ExpandTabs,
    386     rewrite_StripTrailingBlanks,
    387     rewrite_AdjustTrailingLines,
    388     rewrite_SvnNoExecutable,
    389     rewrite_SvnKeywords,
    390     rewrite_C_and_CPP
    391 };
    392 
    393 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
    394 {
    395     rewrite_ForceNativeEol,
    396     rewrite_ExpandTabs,
    397     rewrite_StripTrailingBlanks,
    398     rewrite_AdjustTrailingLines,
    399     rewrite_SvnNoExecutable,
    400     rewrite_C_and_CPP
    401 };
    402 
    403 static PFNSCMREWRITER const g_aRewritersFor_RC[] =
    404 {
    405     rewrite_ForceNativeEol,
    406     rewrite_ExpandTabs,
    407     rewrite_StripTrailingBlanks,
    408     rewrite_AdjustTrailingLines,
    409     rewrite_SvnNoExecutable,
    410     rewrite_SvnKeywords
    411 };
    412 
    413 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
    414 {
    415     rewrite_ForceLF,
    416     rewrite_ExpandTabs,
    417     rewrite_StripTrailingBlanks
    418 };
    419 
    420 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
    421 {
    422     rewrite_ForceCRLF,
    423     rewrite_ExpandTabs,
    424     rewrite_StripTrailingBlanks
    425 };
    426 
    427 static SCMCFGENTRY const g_aConfigs[] =
    428 {
    429     { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
    430     { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
    431     { RT_ELEMENTS(g_aRewritersFor_C_and_CPP),    &g_aRewritersFor_C_and_CPP[0],    "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
    432     { RT_ELEMENTS(g_aRewritersFor_H_and_HPP),    &g_aRewritersFor_H_and_HPP[0],    "*.h|*.hpp" },
    433     { RT_ELEMENTS(g_aRewritersFor_RC),           &g_aRewritersFor_RC[0],           "*.rc" },
    434     { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
    435     { RT_ELEMENTS(g_aRewritersFor_BatchFiles),   &g_aRewritersFor_BatchFiles[0],   "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
    436 };
    437 
    438 
    439 /* -=-=-=-=-=- diff -=-=-=-=-=- */
     34
    44035
    44136
     
    843438}
    844439
    845 
    846 
    847 /* -=-=-=-=-=- settings -=-=-=-=-=- */
    848 
    849 /**
    850  * Init a settings structure with settings from @a pSrc.
    851  *
    852  * @returns IPRT status code
    853  * @param   pSettings           The settings.
    854  * @param   pSrc                The source settings.
    855  */
    856 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
    857 {
    858     *pSettings = *pSrc;
    859 
    860     int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
    861     if (RT_SUCCESS(rc))
    862     {
    863         rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
    864         if (RT_SUCCESS(rc))
    865         {
    866             rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
    867             if (RT_SUCCESS(rc))
    868                 return VINF_SUCCESS;
    869 
    870             RTStrFree(pSettings->pszFilterOutFiles);
    871         }
    872         RTStrFree(pSettings->pszFilterFiles);
    873     }
    874 
    875     pSettings->pszFilterFiles = NULL;
    876     pSettings->pszFilterOutFiles = NULL;
    877     pSettings->pszFilterOutDirs = NULL;
    878     return rc;
    879 }
    880 
    881 /**
    882  * Init a settings structure.
    883  *
    884  * @returns IPRT status code
    885  * @param   pSettings           The settings.
    886  */
    887 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
    888 {
    889     return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
    890 }
    891 
    892 /**
    893  * Deletes the settings, i.e. free any dynamically allocated content.
    894  *
    895  * @param   pSettings           The settings.
    896  */
    897 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
    898 {
    899     if (pSettings)
    900     {
    901         Assert(pSettings->cchTab != ~(unsigned)0);
    902         pSettings->cchTab = ~(unsigned)0;
    903 
    904         RTStrFree(pSettings->pszFilterFiles);
    905         pSettings->pszFilterFiles = NULL;
    906 
    907         RTStrFree(pSettings->pszFilterOutFiles);
    908         pSettings->pszFilterOutFiles = NULL;
    909 
    910         RTStrFree(pSettings->pszFilterOutDirs);
    911         pSettings->pszFilterOutDirs = NULL;
    912     }
    913 }
    914 
    915 
    916 /**
    917  * Processes a RTGetOpt result.
    918  *
    919  * @retval  VINF_SUCCESS if handled.
    920  * @retval  VERR_OUT_OF_RANGE if the option value was out of range.
    921  * @retval  VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
    922  *
    923  * @param   pSettings           The settings to change.
    924  * @param   rc                  The RTGetOpt return value.
    925  * @param   pValueUnion         The RTGetOpt value union.
    926  */
    927 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
    928 {
    929     switch (rc)
    930     {
    931         case SCMOPT_CONVERT_EOL:
    932             pSettings->fConvertEol = true;
    933             return VINF_SUCCESS;
    934         case SCMOPT_NO_CONVERT_EOL:
    935             pSettings->fConvertEol = false;
    936             return VINF_SUCCESS;
    937 
    938         case SCMOPT_CONVERT_TABS:
    939             pSettings->fConvertTabs = true;
    940             return VINF_SUCCESS;
    941         case SCMOPT_NO_CONVERT_TABS:
    942             pSettings->fConvertTabs = false;
    943             return VINF_SUCCESS;
    944 
    945         case SCMOPT_FORCE_FINAL_EOL:
    946             pSettings->fForceFinalEol = true;
    947             return VINF_SUCCESS;
    948         case SCMOPT_NO_FORCE_FINAL_EOL:
    949             pSettings->fForceFinalEol = false;
    950             return VINF_SUCCESS;
    951 
    952         case SCMOPT_FORCE_TRAILING_LINE:
    953             pSettings->fForceTrailingLine = true;
    954             return VINF_SUCCESS;
    955         case SCMOPT_NO_FORCE_TRAILING_LINE:
    956             pSettings->fForceTrailingLine = false;
    957             return VINF_SUCCESS;
    958 
    959         case SCMOPT_STRIP_TRAILING_BLANKS:
    960             pSettings->fStripTrailingBlanks = true;
    961             return VINF_SUCCESS;
    962         case SCMOPT_NO_STRIP_TRAILING_BLANKS:
    963             pSettings->fStripTrailingBlanks = false;
    964             return VINF_SUCCESS;
    965 
    966         case SCMOPT_STRIP_TRAILING_LINES:
    967             pSettings->fStripTrailingLines = true;
    968             return VINF_SUCCESS;
    969         case SCMOPT_NO_STRIP_TRAILING_LINES:
    970             pSettings->fStripTrailingLines = false;
    971             return VINF_SUCCESS;
    972 
    973         case SCMOPT_ONLY_SVN_DIRS:
    974             pSettings->fOnlySvnDirs = true;
    975             return VINF_SUCCESS;
    976         case SCMOPT_NOT_ONLY_SVN_DIRS:
    977             pSettings->fOnlySvnDirs = false;
    978             return VINF_SUCCESS;
    979 
    980         case SCMOPT_ONLY_SVN_FILES:
    981             pSettings->fOnlySvnFiles = true;
    982             return VINF_SUCCESS;
    983         case SCMOPT_NOT_ONLY_SVN_FILES:
    984             pSettings->fOnlySvnFiles = false;
    985             return VINF_SUCCESS;
    986 
    987         case SCMOPT_SET_SVN_EOL:
    988             pSettings->fSetSvnEol = true;
    989             return VINF_SUCCESS;
    990         case SCMOPT_DONT_SET_SVN_EOL:
    991             pSettings->fSetSvnEol = false;
    992             return VINF_SUCCESS;
    993 
    994         case SCMOPT_SET_SVN_EXECUTABLE:
    995             pSettings->fSetSvnExecutable = true;
    996             return VINF_SUCCESS;
    997         case SCMOPT_DONT_SET_SVN_EXECUTABLE:
    998             pSettings->fSetSvnExecutable = false;
    999             return VINF_SUCCESS;
    1000 
    1001         case SCMOPT_SET_SVN_KEYWORDS:
    1002             pSettings->fSetSvnKeywords = true;
    1003             return VINF_SUCCESS;
    1004         case SCMOPT_DONT_SET_SVN_KEYWORDS:
    1005             pSettings->fSetSvnKeywords = false;
    1006             return VINF_SUCCESS;
    1007 
    1008         case SCMOPT_TAB_SIZE:
    1009             if (   pValueUnion->u8 < 1
    1010                 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
    1011             {
    1012                 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
    1013                            pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
    1014                 return VERR_OUT_OF_RANGE;
    1015             }
    1016             pSettings->cchTab = pValueUnion->u8;
    1017             return VINF_SUCCESS;
    1018 
    1019         case SCMOPT_FILTER_OUT_DIRS:
    1020         case SCMOPT_FILTER_FILES:
    1021         case SCMOPT_FILTER_OUT_FILES:
    1022         {
    1023             char **ppsz = NULL;
    1024             switch (rc)
    1025             {
    1026                 case SCMOPT_FILTER_OUT_DIRS:    ppsz = &pSettings->pszFilterOutDirs; break;
    1027                 case SCMOPT_FILTER_FILES:       ppsz = &pSettings->pszFilterFiles; break;
    1028                 case SCMOPT_FILTER_OUT_FILES:   ppsz = &pSettings->pszFilterOutFiles; break;
    1029             }
    1030 
    1031             /*
    1032              * An empty string zaps the current list.
    1033              */
    1034             if (!*pValueUnion->psz)
    1035                 return RTStrATruncate(ppsz, 0);
    1036 
    1037             /*
    1038              * Non-empty strings are appended to the pattern list.
    1039              *
    1040              * Strip leading and trailing pattern separators before attempting
    1041              * to append it.  If it's just separators, don't do anything.
    1042              */
    1043             const char *pszSrc = pValueUnion->psz;
    1044             while (*pszSrc == '|')
    1045                 pszSrc++;
    1046             size_t cchSrc = strlen(pszSrc);
    1047             while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
    1048                 cchSrc--;
    1049             if (!cchSrc)
    1050                 return VINF_SUCCESS;
    1051 
    1052             return RTStrAAppendExN(ppsz, 2,
    1053                                    "|", *ppsz && **ppsz ? 1 : 0,
    1054                                    pszSrc, cchSrc);
    1055         }
    1056 
    1057         default:
    1058             return VERR_GETOPT_UNKNOWN_OPTION;
    1059     }
    1060 }
    1061 
    1062 /**
    1063  * Parses an option string.
    1064  *
    1065  * @returns IPRT status code.
    1066  * @param   pBase               The base settings structure to apply the options
    1067  *                              to.
    1068  * @param   pszOptions          The options to parse.
    1069  */
    1070 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
    1071 {
    1072     int    cArgs;
    1073     char **papszArgs;
    1074     int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
    1075     if (RT_SUCCESS(rc))
    1076     {
    1077         RTGETOPTUNION   ValueUnion;
    1078         RTGETOPTSTATE   GetOptState;
    1079         rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
    1080         if (RT_SUCCESS(rc))
    1081         {
    1082             while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    1083             {
    1084                 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
    1085                 if (RT_FAILURE(rc))
    1086                     break;
    1087             }
    1088         }
    1089         RTGetOptArgvFree(papszArgs);
    1090     }
    1091 
    1092     return rc;
    1093 }
    1094 
    1095 /**
    1096  * Parses an unterminated option string.
    1097  *
    1098  * @returns IPRT status code.
    1099  * @param   pBase               The base settings structure to apply the options
    1100  *                              to.
    1101  * @param   pchLine             The line.
    1102  * @param   cchLine             The line length.
    1103  */
    1104 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
    1105 {
    1106     char *pszLine = RTStrDupN(pchLine, cchLine);
    1107     if (!pszLine)
    1108         return VERR_NO_MEMORY;
    1109     int rc = scmSettingsBaseParseString(pBase, pszLine);
    1110     RTStrFree(pszLine);
    1111     return rc;
    1112 }
    1113 
    1114 /**
    1115  * Verifies the options string.
    1116  *
    1117  * @returns IPRT status code.
    1118  * @param   pszOptions          The options to verify .
    1119  */
    1120 static int scmSettingsBaseVerifyString(const char *pszOptions)
    1121 {
    1122     SCMSETTINGSBASE Base;
    1123     int rc = scmSettingsBaseInit(&Base);
    1124     if (RT_SUCCESS(rc))
    1125     {
    1126         rc = scmSettingsBaseParseString(&Base, pszOptions);
    1127         scmSettingsBaseDelete(&Base);
    1128     }
    1129     return rc;
    1130 }
    1131 
    1132 /**
    1133  * Loads settings found in editor and SCM settings directives within the
    1134  * document (@a pStream).
    1135  *
    1136  * @returns IPRT status code.
    1137  * @param   pBase               The settings base to load settings into.
    1138  * @param   pStream             The stream to scan for settings directives.
    1139  */
    1140 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
    1141 {
    1142     /** @todo Editor and SCM settings directives in documents.  */
    1143     return VINF_SUCCESS;
    1144 }
    1145 
    1146 /**
    1147  * Creates a new settings file struct, cloning @a pSettings.
    1148  *
    1149  * @returns IPRT status code.
    1150  * @param   ppSettings          Where to return the new struct.
    1151  * @param   pSettingsBase       The settings to inherit from.
    1152  */
    1153 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
    1154 {
    1155     PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
    1156     if (!pSettings)
    1157         return VERR_NO_MEMORY;
    1158     int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
    1159     if (RT_SUCCESS(rc))
    1160     {
    1161         pSettings->pDown   = NULL;
    1162         pSettings->pUp     = NULL;
    1163         pSettings->paPairs = NULL;
    1164         pSettings->cPairs  = 0;
    1165         *ppSettings = pSettings;
    1166         return VINF_SUCCESS;
    1167     }
    1168     RTMemFree(pSettings);
    1169     return rc;
    1170 }
    1171 
    1172 /**
    1173  * Destroys a settings structure.
    1174  *
    1175  * @param   pSettings           The settings structure to destroy.  NULL is OK.
    1176  */
    1177 static void scmSettingsDestroy(PSCMSETTINGS pSettings)
    1178 {
    1179     if (pSettings)
    1180     {
    1181         scmSettingsBaseDelete(&pSettings->Base);
    1182         for (size_t i = 0; i < pSettings->cPairs; i++)
    1183         {
    1184             RTStrFree(pSettings->paPairs[i].pszPattern);
    1185             RTStrFree(pSettings->paPairs[i].pszOptions);
    1186             pSettings->paPairs[i].pszPattern = NULL;
    1187             pSettings->paPairs[i].pszOptions = NULL;
    1188         }
    1189         RTMemFree(pSettings->paPairs);
    1190         pSettings->paPairs = NULL;
    1191         RTMemFree(pSettings);
    1192     }
    1193 }
    1194 
    1195 /**
    1196  * Adds a pattern/options pair to the settings structure.
    1197  *
    1198  * @returns IPRT status code.
    1199  * @param   pSettings           The settings.
    1200  * @param   pchLine             The line containing the unparsed pair.
    1201  * @param   cchLine             The length of the line.
    1202  */
    1203 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
    1204 {
    1205     /*
    1206      * Split the string.
    1207      */
    1208     const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
    1209     if (!pchOptions)
    1210         return VERR_INVALID_PARAMETER;
    1211     size_t cchPattern = pchOptions - pchLine;
    1212     size_t cchOptions = cchLine - cchPattern - 1;
    1213     pchOptions++;
    1214 
    1215     /* strip spaces everywhere */
    1216     while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
    1217         cchPattern--;
    1218     while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
    1219         cchPattern--, pchLine++;
    1220 
    1221     while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
    1222         cchOptions--;
    1223     while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
    1224         cchOptions--, pchOptions++;
    1225 
    1226     /* Quietly ignore empty patterns and empty options. */
    1227     if (!cchOptions || !cchPattern)
    1228         return VINF_SUCCESS;
    1229 
    1230     /*
    1231      * Add the pair and verify the option string.
    1232      */
    1233     uint32_t iPair = pSettings->cPairs;
    1234     if ((iPair % 32) == 0)
    1235     {
    1236         void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
    1237         if (!pvNew)
    1238             return VERR_NO_MEMORY;
    1239         pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
    1240     }
    1241 
    1242     pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
    1243     pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
    1244     int rc;
    1245     if (   pSettings->paPairs[iPair].pszPattern
    1246         && pSettings->paPairs[iPair].pszOptions)
    1247         rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
    1248     else
    1249         rc = VERR_NO_MEMORY;
    1250     if (RT_SUCCESS(rc))
    1251         pSettings->cPairs = iPair + 1;
    1252     else
    1253     {
    1254         RTStrFree(pSettings->paPairs[iPair].pszPattern);
    1255         RTStrFree(pSettings->paPairs[iPair].pszOptions);
    1256     }
    1257     return rc;
    1258 }
    1259 
    1260 /**
    1261  * Loads in the settings from @a pszFilename.
    1262  *
    1263  * @returns IPRT status code.
    1264  * @param   pSettings           Where to load the settings file.
    1265  * @param   pszFilename         The file to load.
    1266  */
    1267 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
    1268 {
    1269     SCMSTREAM Stream;
    1270     int rc = ScmStreamInitForReading(&Stream, pszFilename);
    1271     if (RT_FAILURE(rc))
    1272     {
    1273         RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
    1274         return rc;
    1275     }
    1276 
    1277     SCMEOL      enmEol;
    1278     const char *pchLine;
    1279     size_t      cchLine;
    1280     while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    1281     {
    1282         /* Ignore leading spaces. */
    1283         while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
    1284             pchLine++, cchLine--;
    1285 
    1286         /* Ignore empty lines and comment lines. */
    1287         if (cchLine < 1 || *pchLine == '#')
    1288             continue;
    1289 
    1290         /* What kind of line is it? */
    1291         const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
    1292         if (pchColon)
    1293             rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
    1294         else
    1295             rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
    1296         if (RT_FAILURE(rc))
    1297         {
    1298             RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
    1299             break;
    1300         }
    1301     }
    1302 
    1303     if (RT_SUCCESS(rc))
    1304     {
    1305         rc = ScmStreamGetStatus(&Stream);
    1306         if (RT_FAILURE(rc))
    1307             RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
    1308     }
    1309 
    1310     ScmStreamDelete(&Stream);
    1311     return rc;
    1312 }
    1313 
    1314 /**
    1315  * Parse the specified settings file creating a new settings struct from it.
    1316  *
    1317  * @returns IPRT status code
    1318  * @param   ppSettings          Where to return the new settings.
    1319  * @param   pszFilename         The file to parse.
    1320  * @param   pSettingsBase       The base settings we inherit from.
    1321  */
    1322 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
    1323 {
    1324     PSCMSETTINGS pSettings;
    1325     int rc = scmSettingsCreate(&pSettings, pSettingsBase);
    1326     if (RT_SUCCESS(rc))
    1327     {
    1328         rc = scmSettingsLoadFile(pSettings, pszFilename);
    1329         if (RT_SUCCESS(rc))
    1330         {
    1331             *ppSettings = pSettings;
    1332             return VINF_SUCCESS;
    1333         }
    1334 
    1335         scmSettingsDestroy(pSettings);
    1336     }
    1337     *ppSettings = NULL;
    1338     return rc;
    1339 }
    1340 
    1341 
    1342 /**
    1343  * Create an initial settings structure when starting processing a new file or
    1344  * directory.
    1345  *
    1346  * This will look for .scm-settings files from the root and down to the
    1347  * specified directory, combining them into the returned settings structure.
    1348  *
    1349  * @returns IPRT status code.
    1350  * @param   ppSettings          Where to return the pointer to the top stack
    1351  *                              object.
    1352  * @param   pBaseSettings       The base settings we inherit from (globals
    1353  *                              typically).
    1354  * @param   pszPath             The absolute path to the new directory or file.
    1355  */
    1356 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
    1357 {
    1358     *ppSettings = NULL;                 /* try shut up gcc. */
    1359 
    1360     /*
    1361      * We'll be working with a stack copy of the path.
    1362      */
    1363     char    szFile[RTPATH_MAX];
    1364     size_t  cchDir = strlen(pszPath);
    1365     if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
    1366         return VERR_FILENAME_TOO_LONG;
    1367 
    1368     /*
    1369      * Create the bottom-most settings.
    1370      */
    1371     PSCMSETTINGS pSettings;
    1372     int rc = scmSettingsCreate(&pSettings, pBaseSettings);
    1373     if (RT_FAILURE(rc))
    1374         return rc;
    1375 
    1376     /*
    1377      * Enumerate the path components from the root and down. Load any setting
    1378      * files we find.
    1379      */
    1380     size_t cComponents = RTPathCountComponents(pszPath);
    1381     for (size_t i = 1; i <= cComponents; i++)
    1382     {
    1383         rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
    1384         if (RT_SUCCESS(rc))
    1385             rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
    1386         if (RT_FAILURE(rc))
    1387             break;
    1388         if (RTFileExists(szFile))
    1389         {
    1390             rc = scmSettingsLoadFile(pSettings, szFile);
    1391             if (RT_FAILURE(rc))
    1392                 break;
    1393         }
    1394     }
    1395 
    1396     if (RT_SUCCESS(rc))
    1397         *ppSettings = pSettings;
    1398     else
    1399         scmSettingsDestroy(pSettings);
    1400     return rc;
    1401 }
    1402 
    1403 /**
    1404  * Pushes a new settings set onto the stack.
    1405  *
    1406  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1407  *                              element.  This will be used as input and output.
    1408  * @param   pSettings           The settings to push onto the stack.
    1409  */
    1410 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
    1411 {
    1412     PSCMSETTINGS pOld = *ppSettingsStack;
    1413     pSettings->pDown  = pOld;
    1414     pSettings->pUp    = NULL;
    1415     if (pOld)
    1416         pOld->pUp = pSettings;
    1417     *ppSettingsStack = pSettings;
    1418 }
    1419 
    1420 /**
    1421  * Pushes the settings of the specified directory onto the stack.
    1422  *
    1423  * We will load any .scm-settings in the directory.  A stack entry is added even
    1424  * if no settings file was found.
    1425  *
    1426  * @returns IPRT status code.
    1427  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1428  *                              element.  This will be used as input and output.
    1429  * @param   pszDir              The directory to do this for.
    1430  */
    1431 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
    1432 {
    1433     char szFile[RTPATH_MAX];
    1434     int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
    1435     if (RT_SUCCESS(rc))
    1436     {
    1437         PSCMSETTINGS pSettings;
    1438         rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
    1439         if (RT_SUCCESS(rc))
    1440         {
    1441             if (RTFileExists(szFile))
    1442                 rc = scmSettingsLoadFile(pSettings, szFile);
    1443             if (RT_SUCCESS(rc))
    1444             {
    1445                 scmSettingsStackPush(ppSettingsStack, pSettings);
    1446                 return VINF_SUCCESS;
    1447             }
    1448 
    1449             scmSettingsDestroy(pSettings);
    1450         }
    1451     }
    1452     return rc;
    1453 }
    1454 
    1455 
    1456 /**
    1457  * Pops a settings set off the stack.
    1458  *
    1459  * @returns The popped setttings.
    1460  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1461  *                              element.  This will be used as input and output.
    1462  */
    1463 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
    1464 {
    1465     PSCMSETTINGS pRet = *ppSettingsStack;
    1466     PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
    1467     *ppSettingsStack = pNew;
    1468     if (pNew)
    1469         pNew->pUp    = NULL;
    1470     if (pRet)
    1471     {
    1472         pRet->pUp    = NULL;
    1473         pRet->pDown  = NULL;
    1474     }
    1475     return pRet;
    1476 }
    1477 
    1478 /**
    1479  * Pops and destroys the top entry of the stack.
    1480  *
    1481  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1482  *                              element.  This will be used as input and output.
    1483  */
    1484 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
    1485 {
    1486     scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
    1487 }
    1488 
    1489 /**
    1490  * Constructs the base settings for the specified file name.
    1491  *
    1492  * @returns IPRT status code.
    1493  * @param   pSettingsStack      The top element on the settings stack.
    1494  * @param   pszFilename         The file name.
    1495  * @param   pszBasename         The base name (pointer within @a pszFilename).
    1496  * @param   cchBasename         The length of the base name.  (For passing to
    1497  *                              RTStrSimplePatternMultiMatch.)
    1498  * @param   pBase               Base settings to initialize.
    1499  */
    1500 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
    1501                                         const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
    1502 {
    1503     int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
    1504     if (RT_SUCCESS(rc))
    1505     {
    1506         /* find the bottom entry in the stack. */
    1507         PCSCMSETTINGS pCur = pSettingsStack;
    1508         while (pCur->pDown)
    1509             pCur = pCur->pDown;
    1510 
    1511         /* Work our way up thru the stack and look for matching pairs. */
    1512         while (pCur)
    1513         {
    1514             size_t const cPairs = pCur->cPairs;
    1515             if (cPairs)
    1516             {
    1517                 for (size_t i = 0; i < cPairs; i++)
    1518                     if (   RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1519                                                         pszBasename,  cchBasename, NULL)
    1520                         || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1521                                                         pszFilename,  RTSTR_MAX, NULL))
    1522                     {
    1523                         rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
    1524                         if (RT_FAILURE(rc))
    1525                             break;
    1526                     }
    1527                 if (RT_FAILURE(rc))
    1528                     break;
    1529             }
    1530 
    1531             /* advance */
    1532             pCur = pCur->pUp;
    1533         }
    1534     }
    1535     if (RT_FAILURE(rc))
    1536         scmSettingsBaseDelete(pBase);
    1537     return rc;
    1538 }
    1539 
    1540 
    1541 /* -=-=-=-=-=- misc -=-=-=-=-=- */
    1542 
    1543 
    1544 /**
    1545  * Prints a verbose message if the level is high enough.
    1546  *
    1547  * @param   pState              The rewrite state.  Optional.
    1548  * @param   iLevel              The required verbosity level.
    1549  * @param   pszFormat           The message format string.  Can be NULL if we
    1550  *                              only want to trigger the per file message.
    1551  * @param   ...                 Format arguments.
    1552  */
    1553 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
    1554 {
    1555     if (iLevel <= g_iVerbosity)
    1556     {
    1557         if (pState && !pState->fFirst)
    1558         {
    1559             RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
    1560             pState->fFirst = true;
    1561         }
    1562         if (pszFormat)
    1563         {
    1564             RTPrintf(pState
    1565                      ? "%s: info:   "
    1566                      : "%s: info: ",
    1567                      g_szProgName);
    1568             va_list va;
    1569             va_start(va, pszFormat);
    1570             RTPrintfV(pszFormat, va);
    1571             va_end(va);
    1572         }
    1573     }
    1574 }
    1575 
    1576 
    1577 /* -=-=-=-=-=- subversion -=-=-=-=-=- */
    1578 
    1579 #define SCM_WITHOUT_LIBSVN
    1580 
    1581 #ifdef SCM_WITHOUT_LIBSVN
    1582 
    1583 /**
    1584  * Callback that is call for each path to search.
    1585  */
    1586 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    1587 {
    1588     char   *pszDst = (char *)pvUser1;
    1589     size_t  cchDst = (size_t)pvUser2;
    1590     if (cchDst > cchPath)
    1591     {
    1592         memcpy(pszDst, pchPath, cchPath);
    1593         pszDst[cchPath] = '\0';
    1594 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1595         int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
    1596 #else
    1597         int rc = RTPathAppend(pszDst, cchDst, "svn");
    1598 #endif
    1599         if (   RT_SUCCESS(rc)
    1600             && RTFileExists(pszDst))
    1601             return VINF_SUCCESS;
    1602     }
    1603     return VERR_TRY_AGAIN;
    1604 }
    1605 
    1606 
    1607 /**
    1608  * Finds the svn binary.
    1609  *
    1610  * @param   pszPath             Where to store it.  Worst case, we'll return
    1611  *                              "svn" here.
    1612  * @param   cchPath             The size of the buffer pointed to by @a pszPath.
    1613  */
    1614 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
    1615 {
    1616     /** @todo code page fun... */
    1617     Assert(cchPath >= sizeof("svn"));
    1618 #ifdef RT_OS_WINDOWS
    1619     const char *pszEnvVar = RTEnvGet("Path");
    1620 #else
    1621     const char *pszEnvVar = RTEnvGet("PATH");
    1622 #endif
    1623     if (pszPath)
    1624     {
    1625 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1626         int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1627 #else
    1628         int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1629 #endif
    1630         if (RT_SUCCESS(rc))
    1631             return;
    1632     }
    1633     strcpy(pszPath, "svn");
    1634 }
    1635 
    1636 
    1637 /**
    1638  * Construct a dot svn filename for the file being rewritten.
    1639  *
    1640  * @returns IPRT status code.
    1641  * @param   pState              The rewrite state (for the name).
    1642  * @param   pszDir              The directory, including ".svn/".
    1643  * @param   pszSuff             The filename suffix.
    1644  * @param   pszDst              The output buffer.  RTPATH_MAX in size.
    1645  */
    1646 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
    1647 {
    1648     strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
    1649     RTPathStripFilename(pszDst);
    1650 
    1651     int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
    1652     if (RT_SUCCESS(rc))
    1653     {
    1654         rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
    1655         if (RT_SUCCESS(rc))
    1656         {
    1657             size_t cchDst  = strlen(pszDst);
    1658             size_t cchSuff = strlen(pszSuff);
    1659             if (cchDst + cchSuff < RTPATH_MAX)
    1660             {
    1661                 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
    1662                 return VINF_SUCCESS;
    1663             }
    1664             else
    1665                 rc = VERR_BUFFER_OVERFLOW;
    1666         }
    1667     }
    1668     return rc;
    1669 }
    1670 
    1671 /**
    1672  * Interprets the specified string as decimal numbers.
    1673  *
    1674  * @returns true if parsed successfully, false if not.
    1675  * @param   pch                 The string (not terminated).
    1676  * @param   cch                 The string length.
    1677  * @param   pu                  Where to return the value.
    1678  */
    1679 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
    1680 {
    1681     size_t u = 0;
    1682     while (cch-- > 0)
    1683     {
    1684         char ch = *pch++;
    1685         if (ch < '0' || ch > '9')
    1686             return false;
    1687         u *= 10;
    1688         u += ch - '0';
    1689     }
    1690     *pu = u;
    1691     return true;
    1692 }
    1693 
    1694 #endif /* SCM_WITHOUT_LIBSVN */
    1695 
    1696 /**
    1697  * Checks if the file we're operating on is part of a SVN working copy.
    1698  *
    1699  * @returns true if it is, false if it isn't or we cannot tell.
    1700  * @param   pState              The rewrite state to work on.
    1701  */
    1702 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
    1703 {
    1704 #ifdef SCM_WITHOUT_LIBSVN
    1705     /*
    1706      * Hack: check if the .svn/text-base/<file>.svn-base file exists.
    1707      */
    1708     char szPath[RTPATH_MAX];
    1709     int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
    1710     if (RT_SUCCESS(rc))
    1711         return RTFileExists(szPath);
    1712 
    1713 #else
    1714     NOREF(pState);
    1715 #endif
    1716     return false;
    1717 }
    1718 
    1719 /**
    1720  * Queries the value of an SVN property.
    1721  *
    1722  * This will automatically adjust for scheduled changes.
    1723  *
    1724  * @returns IPRT status code.
    1725  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1726  * @retval  VERR_NOT_FOUND if the property wasn't found.
    1727  * @param   pState              The rewrite state to work on.
    1728  * @param   pszName             The property name.
    1729  * @param   ppszValue           Where to return the property value.  Free this
    1730  *                              using RTStrFree.  Optional.
    1731  */
    1732 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
    1733 {
    1734     /*
    1735      * Look it up in the scheduled changes.
    1736      */
    1737     uint32_t i = pState->cSvnPropChanges;
    1738     while (i-- > 0)
    1739         if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
    1740         {
    1741             const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1742             if (!pszValue)
    1743                 return VERR_NOT_FOUND;
    1744             if (ppszValue)
    1745                 return RTStrDupEx(ppszValue, pszValue);
    1746             return VINF_SUCCESS;
    1747         }
    1748 
    1749 #ifdef SCM_WITHOUT_LIBSVN
    1750     /*
    1751      * Hack: Read the .svn/props/<file>.svn-work file exists.
    1752      */
    1753     char szPath[RTPATH_MAX];
    1754     int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
    1755     if (RT_SUCCESS(rc) && !RTFileExists(szPath))
    1756         rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
    1757     if (RT_SUCCESS(rc))
    1758     {
    1759         SCMSTREAM Stream;
    1760         rc = ScmStreamInitForReading(&Stream, szPath);
    1761         if (RT_SUCCESS(rc))
    1762         {
    1763             /*
    1764              * The current format is K len\n<name>\nV len\n<value>\n" ... END.
    1765              */
    1766             rc = VERR_NOT_FOUND;
    1767             size_t const    cchName = strlen(pszName);
    1768             SCMEOL          enmEol;
    1769             size_t          cchLine;
    1770             const char     *pchLine;
    1771             while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    1772             {
    1773                 /*
    1774                  * Parse the 'K num' / 'END' line.
    1775                  */
    1776                 if (   cchLine == 3
    1777                     && !memcmp(pchLine, "END", 3))
    1778                     break;
    1779                 size_t cchKey;
    1780                 if (   cchLine < 3
    1781                     || pchLine[0] != 'K'
    1782                     || pchLine[1] != ' '
    1783                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
    1784                     || cchKey == 0
    1785                     || cchKey > 4096)
    1786                 {
    1787                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1788                     rc = VERR_PARSE_ERROR;
    1789                     break;
    1790                 }
    1791 
    1792                 /*
    1793                  * Match the key and skip to the value line.  Don't bother with
    1794                  * names containing EOL markers.
    1795                  */
    1796                 size_t const offKey = ScmStreamTell(&Stream);
    1797                 bool fMatch = cchName == cchKey;
    1798                 if (fMatch)
    1799                 {
    1800                     pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1801                     if (!pchLine)
    1802                         break;
    1803                     fMatch = cchLine == cchName
    1804                           && !memcmp(pchLine, pszName, cchName);
    1805                 }
    1806 
    1807                 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
    1808                     break;
    1809                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1810                     break;
    1811 
    1812                 /*
    1813                  * Read and Parse the 'V num' line.
    1814                  */
    1815                 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1816                 if (!pchLine)
    1817                     break;
    1818                 size_t cchValue;
    1819                 if (   cchLine < 3
    1820                     || pchLine[0] != 'V'
    1821                     || pchLine[1] != ' '
    1822                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
    1823                     || cchValue > _1M)
    1824                 {
    1825                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1826                     rc = VERR_PARSE_ERROR;
    1827                     break;
    1828                 }
    1829 
    1830                 /*
    1831                  * If we have a match, allocate a return buffer and read the
    1832                  * value into it.  Otherwise skip this value and continue
    1833                  * searching.
    1834                  */
    1835                 if (fMatch)
    1836                 {
    1837                     if (!ppszValue)
    1838                         rc = VINF_SUCCESS;
    1839                     else
    1840                     {
    1841                         char *pszValue;
    1842                         rc = RTStrAllocEx(&pszValue, cchValue + 1);
    1843                         if (RT_SUCCESS(rc))
    1844                         {
    1845                             rc = ScmStreamRead(&Stream, pszValue, cchValue);
    1846                             if (RT_SUCCESS(rc))
    1847                                 *ppszValue = pszValue;
    1848                             else
    1849                                 RTStrFree(pszValue);
    1850                         }
    1851                     }
    1852                     break;
    1853                 }
    1854 
    1855                 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
    1856                     break;
    1857                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1858                     break;
    1859             }
    1860 
    1861             if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
    1862             {
    1863                 rc = ScmStreamGetStatus(&Stream);
    1864                 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
    1865             }
    1866             ScmStreamDelete(&Stream);
    1867         }
    1868     }
    1869 
    1870     if (rc == VERR_FILE_NOT_FOUND)
    1871         rc = VERR_NOT_FOUND;
    1872     return rc;
    1873 
    1874 #else
    1875     NOREF(pState);
    1876 #endif
    1877     return VERR_NOT_FOUND;
    1878 }
    1879 
    1880 
    1881 /**
    1882  * Schedules the setting of a property.
    1883  *
    1884  * @returns IPRT status code.
    1885  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1886  * @param   pState              The rewrite state to work on.
    1887  * @param   pszName             The name of the property to set.
    1888  * @param   pszValue            The value.  NULL means deleting it.
    1889  */
    1890 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
    1891 {
    1892     /*
    1893      * Update any existing entry first.
    1894      */
    1895     size_t i = pState->cSvnPropChanges;
    1896     while (i-- > 0)
    1897         if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
    1898         {
    1899             if (!pszValue)
    1900             {
    1901                 RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1902                 pState->paSvnPropChanges[i].pszValue = NULL;
    1903             }
    1904             else
    1905             {
    1906                 char *pszCopy;
    1907                 int rc = RTStrDupEx(&pszCopy, pszValue);
    1908                 if (RT_FAILURE(rc))
    1909                     return rc;
    1910                 pState->paSvnPropChanges[i].pszValue = pszCopy;
    1911             }
    1912             return VINF_SUCCESS;
    1913         }
    1914 
    1915     /*
    1916      * Insert a new entry.
    1917      */
    1918     i = pState->cSvnPropChanges;
    1919     if ((i % 32) == 0)
    1920     {
    1921         void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
    1922         if (!pvNew)
    1923             return VERR_NO_MEMORY;
    1924         pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
    1925     }
    1926 
    1927     pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
    1928     pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
    1929     if (   pState->paSvnPropChanges[i].pszName
    1930         && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
    1931         pState->cSvnPropChanges = i + 1;
    1932     else
    1933     {
    1934         RTStrFree(pState->paSvnPropChanges[i].pszName);
    1935         pState->paSvnPropChanges[i].pszName = NULL;
    1936         RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1937         pState->paSvnPropChanges[i].pszValue = NULL;
    1938         return VERR_NO_MEMORY;
    1939     }
    1940     return VINF_SUCCESS;
    1941 }
    1942 
    1943 
    1944 /**
    1945  * Schedules a property deletion.
    1946  *
    1947  * @returns IPRT status code.
    1948  * @param   pState              The rewrite state to work on.
    1949  * @param   pszName             The name of the property to delete.
    1950  */
    1951 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
    1952 {
    1953     return scmSvnSetProperty(pState, pszName, NULL);
    1954 }
    1955 
    1956 
    1957 /**
    1958  * Applies any SVN property changes to the work copy of the file.
    1959  *
    1960  * @returns IPRT status code.
    1961  * @param   pState              The rewrite state which SVN property changes
    1962  *                              should be applied.
    1963  */
    1964 static int scmSvnDisplayChanges(PSCMRWSTATE pState)
    1965 {
    1966     size_t i = pState->cSvnPropChanges;
    1967     while (i-- > 0)
    1968     {
    1969         const char *pszName  = pState->paSvnPropChanges[i].pszName;
    1970         const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1971         if (pszValue)
    1972             ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1973         else
    1974             ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1975     }
    1976 
    1977     return VINF_SUCCESS;
    1978 }
    1979 
    1980 /**
    1981  * Applies any SVN property changes to the work copy of the file.
    1982  *
    1983  * @returns IPRT status code.
    1984  * @param   pState              The rewrite state which SVN property changes
    1985  *                              should be applied.
    1986  */
    1987 static int scmSvnApplyChanges(PSCMRWSTATE pState)
    1988 {
    1989 #ifdef SCM_WITHOUT_LIBSVN
    1990     /*
    1991      * This sucks. We gotta find svn(.exe).
    1992      */
    1993     static char s_szSvnPath[RTPATH_MAX];
    1994     if (s_szSvnPath[0] == '\0')
    1995         scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
    1996 
    1997     /*
    1998      * Iterate thru the changes and apply them by starting the svn client.
    1999      */
    2000     for (size_t i = 0; i <pState->cSvnPropChanges; i++)
    2001     {
    2002         const char *apszArgv[6];
    2003         apszArgv[0] = s_szSvnPath;
    2004         apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
    2005         apszArgv[2] = pState->paSvnPropChanges[i].pszName;
    2006         int iArg = 3;
    2007         if (pState->paSvnPropChanges[i].pszValue)
    2008             apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
    2009         apszArgv[iArg++] = pState->pszFilename;
    2010         apszArgv[iArg++] = NULL;
    2011         ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
    2012                    apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
    2013 
    2014         RTPROCESS pid;
    2015         int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
    2016         if (RT_SUCCESS(rc))
    2017         {
    2018             RTPROCSTATUS Status;
    2019             rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
    2020             if (    RT_SUCCESS(rc)
    2021                 &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
    2022                      || Status.iStatus != 0) )
    2023             {
    2024                 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
    2025                            pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
    2026                            Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
    2027                            : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
    2028                            : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
    2029                            : "abducted by alien",
    2030                            Status.iStatus);
    2031                 return VERR_GENERAL_FAILURE;
    2032             }
    2033         }
    2034         if (RT_FAILURE(rc))
    2035         {
    2036             RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
    2037                        pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
    2038             return rc;
    2039         }
    2040     }
    2041 
    2042     return VINF_SUCCESS;
    2043 #else
    2044     return VERR_NOT_IMPLEMENTED;
    2045 #endif
    2046 }
    2047 
    2048 
    2049 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */
    2050 
    2051 
    2052 /**
    2053  * Strip trailing blanks (space & tab).
    2054  *
    2055  * @returns True if modified, false if not.
    2056  * @param   pIn                 The input stream.
    2057  * @param   pOut                The output stream.
    2058  * @param   pSettings           The settings.
    2059  */
    2060 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2061 {
    2062     if (!pSettings->fStripTrailingBlanks)
    2063         return false;
    2064 
    2065     bool        fModified = false;
    2066     SCMEOL      enmEol;
    2067     size_t      cchLine;
    2068     const char *pchLine;
    2069     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2070     {
    2071         int rc;
    2072         if (    cchLine == 0
    2073             ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
    2074             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2075         else
    2076         {
    2077             cchLine--;
    2078             while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
    2079                 cchLine--;
    2080             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2081             fModified = true;
    2082         }
    2083         if (RT_FAILURE(rc))
    2084             return false;
    2085     }
    2086     if (fModified)
    2087         ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
    2088     return fModified;
    2089 }
    2090 
    2091 /**
    2092  * Expand tabs.
    2093  *
    2094  * @returns True if modified, false if not.
    2095  * @param   pIn                 The input stream.
    2096  * @param   pOut                The output stream.
    2097  * @param   pSettings           The settings.
    2098  */
    2099 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2100 {
    2101     if (!pSettings->fConvertTabs)
    2102         return false;
    2103 
    2104     size_t const    cchTab = pSettings->cchTab;
    2105     bool            fModified = false;
    2106     SCMEOL          enmEol;
    2107     size_t          cchLine;
    2108     const char     *pchLine;
    2109     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2110     {
    2111         int rc;
    2112         const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
    2113         if (!pchTab)
    2114             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2115         else
    2116         {
    2117             size_t      offTab   = 0;
    2118             const char *pchChunk = pchLine;
    2119             for (;;)
    2120             {
    2121                 size_t  cchChunk = pchTab - pchChunk;
    2122                 offTab += cchChunk;
    2123                 ScmStreamWrite(pOut, pchChunk, cchChunk);
    2124 
    2125                 size_t  cchToTab = cchTab - offTab % cchTab;
    2126                 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
    2127                 offTab += cchToTab;
    2128 
    2129                 pchChunk = pchTab + 1;
    2130                 size_t  cchLeft  = cchLine - (pchChunk - pchLine);
    2131                 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
    2132                 if (!pchTab)
    2133                 {
    2134                     rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
    2135                     break;
    2136                 }
    2137             }
    2138 
    2139             fModified = true;
    2140         }
    2141         if (RT_FAILURE(rc))
    2142             return false;
    2143     }
    2144     if (fModified)
    2145         ScmVerbose(pState, 2, " * Expanded tabs\n");
    2146     return fModified;
    2147 }
    2148 
    2149 /**
    2150  * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
    2151  *
    2152  * @returns true if modifications were made, false if not.
    2153  * @param   pIn                 The input stream.
    2154  * @param   pOut                The output stream.
    2155  * @param   pSettings           The settings.
    2156  * @param   enmDesiredEol       The desired end of line indicator type.
    2157  * @param   pszDesiredSvnEol    The desired svn:eol-style.
    2158  */
    2159 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
    2160                              SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
    2161 {
    2162     if (!pSettings->fConvertEol)
    2163         return false;
    2164 
    2165     bool        fModified = false;
    2166     SCMEOL      enmEol;
    2167     size_t      cchLine;
    2168     const char *pchLine;
    2169     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2170     {
    2171         if (   enmEol != enmDesiredEol
    2172             && enmEol != SCMEOL_NONE)
    2173         {
    2174             fModified = true;
    2175             enmEol = enmDesiredEol;
    2176         }
    2177         int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2178         if (RT_FAILURE(rc))
    2179             return false;
    2180     }
    2181     if (fModified)
    2182         ScmVerbose(pState, 2, " * Converted EOL markers\n");
    2183 
    2184     /* Check svn:eol-style if appropriate */
    2185     if (   pSettings->fSetSvnEol
    2186         && scmSvnIsInWorkingCopy(pState))
    2187     {
    2188         char *pszEol;
    2189         int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    2190         if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    2191             || rc == VERR_NOT_FOUND)
    2192         {
    2193             if (rc == VERR_NOT_FOUND)
    2194                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
    2195             else
    2196                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    2197             int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    2198             if (RT_FAILURE(rc2))
    2199                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
    2200         }
    2201         if (RT_SUCCESS(rc))
    2202             RTStrFree(pszEol);
    2203     }
    2204 
    2205     /** @todo also check the subversion svn:eol-style state! */
    2206     return fModified;
    2207 }
    2208 
    2209 /**
    2210  * Force native end of line indicator.
    2211  *
    2212  * @returns true if modifications were made, false if not.
    2213  * @param   pIn                 The input stream.
    2214  * @param   pOut                The output stream.
    2215  * @param   pSettings           The settings.
    2216  */
    2217 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2218 {
    2219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2220     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
    2221 #else
    2222     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
    2223 #endif
    2224 }
    2225 
    2226 /**
    2227  * Force the stream to use LF as the end of line indicator.
    2228  *
    2229  * @returns true if modifications were made, false if not.
    2230  * @param   pIn                 The input stream.
    2231  * @param   pOut                The output stream.
    2232  * @param   pSettings           The settings.
    2233  */
    2234 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2235 {
    2236     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
    2237 }
    2238 
    2239 /**
    2240  * Force the stream to use CRLF as the end of line indicator.
    2241  *
    2242  * @returns true if modifications were made, false if not.
    2243  * @param   pIn                 The input stream.
    2244  * @param   pOut                The output stream.
    2245  * @param   pSettings           The settings.
    2246  */
    2247 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2248 {
    2249     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
    2250 }
    2251 
    2252 /**
    2253  * Strip trailing blank lines and/or make sure there is exactly one blank line
    2254  * at the end of the file.
    2255  *
    2256  * @returns true if modifications were made, false if not.
    2257  * @param   pIn                 The input stream.
    2258  * @param   pOut                The output stream.
    2259  * @param   pSettings           The settings.
    2260  *
    2261  * @remarks ASSUMES trailing white space has been removed already.
    2262  */
    2263 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2264 {
    2265     if (   !pSettings->fStripTrailingLines
    2266         && !pSettings->fForceTrailingLine
    2267         && !pSettings->fForceFinalEol)
    2268         return false;
    2269 
    2270     size_t const cLines = ScmStreamCountLines(pIn);
    2271 
    2272     /* Empty files remains empty. */
    2273     if (cLines <= 1)
    2274         return false;
    2275 
    2276     /* Figure out if we need to adjust the number of lines or not. */
    2277     size_t cLinesNew = cLines;
    2278 
    2279     if (   pSettings->fStripTrailingLines
    2280         && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    2281     {
    2282         while (   cLinesNew > 1
    2283                && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
    2284             cLinesNew--;
    2285     }
    2286 
    2287     if (    pSettings->fForceTrailingLine
    2288         && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    2289         cLinesNew++;
    2290 
    2291     bool fFixMissingEol = pSettings->fForceFinalEol
    2292                        && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
    2293 
    2294     if (   !fFixMissingEol
    2295         && cLines == cLinesNew)
    2296         return false;
    2297 
    2298     /* Copy the number of lines we've arrived at. */
    2299     ScmStreamRewindForReading(pIn);
    2300 
    2301     size_t cCopied = RT_MIN(cLinesNew, cLines);
    2302     ScmStreamCopyLines(pOut, pIn, cCopied);
    2303 
    2304     if (cCopied != cLinesNew)
    2305     {
    2306         while (cCopied++ < cLinesNew)
    2307             ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
    2308     }
    2309     /* Fix missing EOL if required. */
    2310     else if (fFixMissingEol)
    2311     {
    2312         if (ScmStreamGetEol(pIn) == SCMEOL_LF)
    2313             ScmStreamWrite(pOut, "\n", 1);
    2314         else
    2315             ScmStreamWrite(pOut, "\r\n", 2);
    2316     }
    2317 
    2318     ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
    2319     return true;
    2320 }
    2321 
    2322 /**
    2323  * Make sure there is no svn:executable keyword on the current file.
    2324  *
    2325  * @returns false - the state carries these kinds of changes.
    2326  * @param   pState              The rewriter state.
    2327  * @param   pIn                 The input stream.
    2328  * @param   pOut                The output stream.
    2329  * @param   pSettings           The settings.
    2330  */
    2331 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2332 {
    2333     if (   !pSettings->fSetSvnExecutable
    2334         || !scmSvnIsInWorkingCopy(pState))
    2335         return false;
    2336 
    2337     int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
    2338     if (RT_SUCCESS(rc))
    2339     {
    2340         ScmVerbose(pState, 2, " * removing svn:executable\n");
    2341         rc = scmSvnDelProperty(pState, "svn:executable");
    2342         if (RT_FAILURE(rc))
    2343             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2344     }
    2345     return false;
    2346 }
    2347 
    2348 /**
    2349  * Make sure the Id and Revision keywords are expanded.
    2350  *
    2351  * @returns false - the state carries these kinds of changes.
    2352  * @param   pState              The rewriter state.
    2353  * @param   pIn                 The input stream.
    2354  * @param   pOut                The output stream.
    2355  * @param   pSettings           The settings.
    2356  */
    2357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2358 {
    2359     if (   !pSettings->fSetSvnKeywords
    2360         || !scmSvnIsInWorkingCopy(pState))
    2361         return false;
    2362 
    2363     char *pszKeywords;
    2364     int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    2365     if (    RT_SUCCESS(rc)
    2366         && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
    2367             || !strstr(pszKeywords, "Revision")) )
    2368     {
    2369         if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
    2370             rc = RTStrAAppend(&pszKeywords, " Id Revision");
    2371         else if (!strstr(pszKeywords, "Id"))
    2372             rc = RTStrAAppend(&pszKeywords, " Id");
    2373         else
    2374             rc = RTStrAAppend(&pszKeywords, " Revision");
    2375         if (RT_SUCCESS(rc))
    2376         {
    2377             ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    2378             rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    2379             if (RT_FAILURE(rc))
    2380                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2381         }
    2382         else
    2383             RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
    2384         RTStrFree(pszKeywords);
    2385     }
    2386     else if (rc == VERR_NOT_FOUND)
    2387     {
    2388         ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    2389         rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    2390         if (RT_FAILURE(rc))
    2391             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2392     }
    2393     else if (RT_SUCCESS(rc))
    2394         RTStrFree(pszKeywords);
    2395 
    2396     return false;
    2397 }
    2398 
    2399 /**
    2400  * Makefile.kup are empty files, enforce this.
    2401  *
    2402  * @returns true if modifications were made, false if not.
    2403  * @param   pIn                 The input stream.
    2404  * @param   pOut                The output stream.
    2405  * @param   pSettings           The settings.
    2406  */
    2407 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2408 {
    2409     /* These files should be zero bytes. */
    2410     if (pIn->cb == 0)
    2411         return false;
    2412     ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
    2413     return true;
    2414 }
    2415 
    2416 /**
    2417  * Rewrite a kBuild makefile.
    2418  *
    2419  * @returns true if modifications were made, false if not.
    2420  * @param   pIn                 The input stream.
    2421  * @param   pOut                The output stream.
    2422  * @param   pSettings           The settings.
    2423  *
    2424  * @todo
    2425  *
    2426  * Ideas for Makefile.kmk and Config.kmk:
    2427  *      - sort if1of/ifn1of sets.
    2428  *      - line continuation slashes should only be preceded by one space.
    2429  */
    2430 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2431 {
    2432     return false;
    2433 }
    2434 
    2435 /**
    2436  * Rewrite a C/C++ source or header file.
    2437  *
    2438  * @returns true if modifications were made, false if not.
    2439  * @param   pIn                 The input stream.
    2440  * @param   pOut                The output stream.
    2441  * @param   pSettings           The settings.
    2442  *
    2443  * @todo
    2444  *
    2445  * Ideas for C/C++:
    2446  *      - space after if, while, for, switch
    2447  *      - spaces in for (i=0;i<x;i++)
    2448  *      - complex conditional, bird style.
    2449  *      - remove unnecessary parentheses.
    2450  *      - sort defined RT_OS_*||  and RT_ARCH
    2451  *      - sizeof without parenthesis.
    2452  *      - defined without parenthesis.
    2453  *      - trailing spaces.
    2454  *      - parameter indentation.
    2455  *      - space after comma.
    2456  *      - while (x--); -> multi line + comment.
    2457  *      - else statement;
    2458  *      - space between function and left parenthesis.
    2459  *      - TODO, XXX, @todo cleanup.
    2460  *      - Space before/after '*'.
    2461  *      - ensure new line at end of file.
    2462  *      - Indentation of precompiler statements (#ifdef, #defines).
    2463  *      - space between functions.
    2464  *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    2465  */
    2466 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2467 {
    2468 
    2469     return false;
    2470 }
    2471 
    2472 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
    2473 
    2474 /**
    2475  * Processes a file.
    2476  *
    2477  * @returns IPRT status code.
    2478  * @param   pState              The rewriter state.
    2479  * @param   pszFilename         The file name.
    2480  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2481  * @param   cchBasename         The length of the base name.  (For passing to
    2482  *                              RTStrSimplePatternMultiMatch.)
    2483  * @param   pBaseSettings       The base settings to use.  It's OK to modify
    2484  *                              these.
    2485  */
    2486 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2487                                PSCMSETTINGSBASE pBaseSettings)
    2488 {
    2489     /*
    2490      * Do the file level filtering.
    2491      */
    2492     if (   pBaseSettings->pszFilterFiles
    2493         && *pBaseSettings->pszFilterFiles
    2494         && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2495     {
    2496         ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
    2497         return VINF_SUCCESS;
    2498     }
    2499     if (   pBaseSettings->pszFilterOutFiles
    2500         && *pBaseSettings->pszFilterOutFiles
    2501         && (   RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
    2502             || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
    2503     {
    2504         ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
    2505         return VINF_SUCCESS;
    2506     }
    2507     if (   pBaseSettings->fOnlySvnFiles
    2508         && !scmSvnIsInWorkingCopy(pState))
    2509     {
    2510         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
    2511         return VINF_SUCCESS;
    2512     }
    2513 
    2514     /*
    2515      * Try find a matching rewrite config for this filename.
    2516      */
    2517     PCSCMCFGENTRY pCfg = NULL;
    2518     for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
    2519         if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2520         {
    2521             pCfg = &g_aConfigs[iCfg];
    2522             break;
    2523         }
    2524     if (!pCfg)
    2525     {
    2526         ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
    2527         return VINF_SUCCESS;
    2528     }
    2529     ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
    2530 
    2531     /*
    2532      * Create an input stream from the file and check that it's text.
    2533      */
    2534     SCMSTREAM Stream1;
    2535     int rc = ScmStreamInitForReading(&Stream1, pszFilename);
    2536     if (RT_FAILURE(rc))
    2537     {
    2538         RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
    2539         return rc;
    2540     }
    2541     if (ScmStreamIsText(&Stream1))
    2542     {
    2543         ScmVerbose(pState, 3, NULL);
    2544 
    2545         /*
    2546          * Gather SCM and editor settings from the stream.
    2547          */
    2548         rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
    2549         if (RT_SUCCESS(rc))
    2550         {
    2551             ScmStreamRewindForReading(&Stream1);
    2552 
    2553             /*
    2554              * Create two more streams for output and push the text thru all the
    2555              * rewriters, switching the two streams around when something is
    2556              * actually rewritten.  Stream1 remains unchanged.
    2557              */
    2558             SCMSTREAM Stream2;
    2559             rc = ScmStreamInitForWriting(&Stream2, &Stream1);
    2560             if (RT_SUCCESS(rc))
    2561             {
    2562                 SCMSTREAM Stream3;
    2563                 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
    2564                 if (RT_SUCCESS(rc))
    2565                 {
    2566                     bool        fModified = false;
    2567                     PSCMSTREAM  pIn       = &Stream1;
    2568                     PSCMSTREAM  pOut      = &Stream2;
    2569                     for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
    2570                     {
    2571                         bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
    2572                         if (fRc)
    2573                         {
    2574                             PSCMSTREAM pTmp = pOut;
    2575                             pOut = pIn == &Stream1 ? &Stream3 : pIn;
    2576                             pIn  = pTmp;
    2577                             fModified = true;
    2578                         }
    2579                         ScmStreamRewindForReading(pIn);
    2580                         ScmStreamRewindForWriting(pOut);
    2581                     }
    2582 
    2583                     rc = ScmStreamGetStatus(&Stream1);
    2584                     if (RT_SUCCESS(rc))
    2585                         rc = ScmStreamGetStatus(&Stream2);
    2586                     if (RT_SUCCESS(rc))
    2587                         rc = ScmStreamGetStatus(&Stream3);
    2588                     if (RT_SUCCESS(rc))
    2589                     {
    2590                         /*
    2591                          * If rewritten, write it back to disk.
    2592                          */
    2593                         if (fModified)
    2594                         {
    2595                             if (!g_fDryRun)
    2596                             {
    2597                                 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2598                                 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
    2599                                 if (RT_FAILURE(rc))
    2600                                     RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
    2601                             }
    2602                             else
    2603                             {
    2604                                 ScmVerbose(pState, 1, NULL);
    2605                                 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
    2606                                                g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
    2607                                 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2608                             }
    2609                         }
    2610 
    2611                         /*
    2612                          * If pending SVN property changes, apply them.
    2613                          */
    2614                         if (pState->cSvnPropChanges && RT_SUCCESS(rc))
    2615                         {
    2616                             if (!g_fDryRun)
    2617                             {
    2618                                 rc = scmSvnApplyChanges(pState);
    2619                                 if (RT_FAILURE(rc))
    2620                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
    2621                             }
    2622                             else
    2623                                 scmSvnDisplayChanges(pState);
    2624                         }
    2625 
    2626                         if (!fModified && !pState->cSvnPropChanges)
    2627                             ScmVerbose(pState, 3, "no change\n", pszFilename);
    2628                     }
    2629                     else
    2630                         RTMsgError("%s: stream error %Rrc\n", pszFilename);
    2631                     ScmStreamDelete(&Stream3);
    2632                 }
    2633                 else
    2634                     RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2635                 ScmStreamDelete(&Stream2);
    2636             }
    2637             else
    2638                 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2639         }
    2640         else
    2641             RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
    2642     }
    2643     else
    2644         ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
    2645     ScmStreamDelete(&Stream1);
    2646 
    2647     return rc;
    2648 }
    2649 
    2650 /**
    2651  * Processes a file.
    2652  *
    2653  * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
    2654  * directory recursion method.
    2655  *
    2656  * @returns IPRT status code.
    2657  * @param   pszFilename         The file name.
    2658  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2659  * @param   cchBasename         The length of the base name.  (For passing to
    2660  *                              RTStrSimplePatternMultiMatch.)
    2661  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2662  */
    2663 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2664                           PSCMSETTINGS pSettingsStack)
    2665 {
    2666     SCMSETTINGSBASE Base;
    2667     int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
    2668     if (RT_SUCCESS(rc))
    2669     {
    2670         SCMRWSTATE State;
    2671         State.fFirst           = false;
    2672         State.pszFilename      = pszFilename;
    2673         State.cSvnPropChanges  = 0;
    2674         State.paSvnPropChanges = NULL;
    2675 
    2676         rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
    2677 
    2678         size_t i = State.cSvnPropChanges;
    2679         while (i-- > 0)
    2680         {
    2681             RTStrFree(State.paSvnPropChanges[i].pszName);
    2682             RTStrFree(State.paSvnPropChanges[i].pszValue);
    2683         }
    2684         RTMemFree(State.paSvnPropChanges);
    2685 
    2686         scmSettingsBaseDelete(&Base);
    2687     }
    2688     return rc;
    2689 }
    2690 
    2691 
    2692 /**
    2693  * Tries to correct RTDIRENTRY_UNKNOWN.
    2694  *
    2695  * @returns Corrected type.
    2696  * @param   pszPath             The path to the object in question.
    2697  */
    2698 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
    2699 {
    2700     RTFSOBJINFO Info;
    2701     int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
    2702     if (RT_FAILURE(rc))
    2703         return RTDIRENTRYTYPE_UNKNOWN;
    2704     if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
    2705         return RTDIRENTRYTYPE_DIRECTORY;
    2706     if (RTFS_IS_FILE(Info.Attr.fMode))
    2707         return RTDIRENTRYTYPE_FILE;
    2708     return RTDIRENTRYTYPE_UNKNOWN;
    2709 }
    2710 
    2711 /**
    2712  * Recurse into a sub-directory and process all the files and directories.
    2713  *
    2714  * @returns IPRT status code.
    2715  * @param   pszBuf              Path buffer containing the directory path on
    2716  *                              entry.  This ends with a dot.  This is passed
    2717  *                              along when recursing in order to save stack space
    2718  *                              and avoid needless copying.
    2719  * @param   cchDir              Length of our path in pszbuf.
    2720  * @param   pEntry              Directory entry buffer.  This is also passed
    2721  *                              along when recursing to save stack space.
    2722  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2723  * @param   iRecursion          The recursion depth.  This is used to restrict
    2724  *                              the recursions.
    2725  */
    2726 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
    2727                                       PSCMSETTINGS pSettingsStack, unsigned iRecursion)
    2728 {
    2729     int rc;
    2730     Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
    2731 
    2732     /*
    2733      * Make sure we stop somewhere.
    2734      */
    2735     if (iRecursion > 128)
    2736     {
    2737         RTMsgError("recursion too deep: %d\n", iRecursion);
    2738         return VINF_SUCCESS; /* ignore */
    2739     }
    2740 
    2741     /*
    2742      * Check if it's excluded by --only-svn-dir.
    2743      */
    2744     if (pSettingsStack->Base.fOnlySvnDirs)
    2745     {
    2746         rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
    2747         if (RT_FAILURE(rc))
    2748         {
    2749             RTMsgError("RTPathAppend: %Rrc\n", rc);
    2750             return rc;
    2751         }
    2752         if (!RTDirExists(pszBuf))
    2753             return VINF_SUCCESS;
    2754 
    2755         Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
    2756         pszBuf[cchDir]     = '\0';
    2757         pszBuf[cchDir - 1] = '.';
    2758     }
    2759 
    2760     /*
    2761      * Try open and read the directory.
    2762      */
    2763     PRTDIR pDir;
    2764     rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
    2765     if (RT_FAILURE(rc))
    2766     {
    2767         RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
    2768         return rc;
    2769     }
    2770     for (;;)
    2771     {
    2772         /* Read the next entry. */
    2773         rc = RTDirRead(pDir, pEntry, NULL);
    2774         if (RT_FAILURE(rc))
    2775         {
    2776             if (rc == VERR_NO_MORE_FILES)
    2777                 rc = VINF_SUCCESS;
    2778             else
    2779                 RTMsgError("RTDirRead -> %Rrc\n", rc);
    2780             break;
    2781         }
    2782 
    2783         /* Skip '.' and '..'. */
    2784         if (    pEntry->szName[0] == '.'
    2785             &&  (   pEntry->cbName == 1
    2786                  || (   pEntry->cbName == 2
    2787                      && pEntry->szName[1] == '.')))
    2788             continue;
    2789 
    2790         /* Enter it into the buffer so we've got a full name to work
    2791            with when needed. */
    2792         if (pEntry->cbName + cchDir >= RTPATH_MAX)
    2793         {
    2794             RTMsgError("Skipping too long entry: %s", pEntry->szName);
    2795             continue;
    2796         }
    2797         memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
    2798 
    2799         /* Figure the type. */
    2800         RTDIRENTRYTYPE enmType = pEntry->enmType;
    2801         if (enmType == RTDIRENTRYTYPE_UNKNOWN)
    2802             enmType = scmFigureUnknownType(pszBuf);
    2803 
    2804         /* Process the file or directory, skip the rest. */
    2805         if (enmType == RTDIRENTRYTYPE_FILE)
    2806             rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
    2807         else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
    2808         {
    2809             /* Append the dot for the benefit of the pattern matching. */
    2810             if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
    2811             {
    2812                 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
    2813                 continue;
    2814             }
    2815             memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
    2816             size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
    2817 
    2818             if (   !pSettingsStack->Base.pszFilterOutDirs
    2819                 || !*pSettingsStack->Base.pszFilterOutDirs
    2820                 || (   !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2821                                                      pEntry->szName, pEntry->cbName, NULL)
    2822                     && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2823                                                      pszBuf, cchSubDir, NULL)
    2824                    )
    2825                )
    2826             {
    2827                 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
    2828                 if (RT_SUCCESS(rc))
    2829                 {
    2830                     rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
    2831                     scmSettingsStackPopAndDestroy(&pSettingsStack);
    2832                 }
    2833             }
    2834         }
    2835         if (RT_FAILURE(rc))
    2836             break;
    2837     }
    2838     RTDirClose(pDir);
    2839     return rc;
    2840 
    2841 }
    2842 
    2843 /**
    2844  * Process a directory tree.
    2845  *
    2846  * @returns IPRT status code.
    2847  * @param   pszDir              The directory to start with.  This is pointer to
    2848  *                              a RTPATH_MAX sized buffer.
    2849  */
    2850 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
    2851 {
    2852     /*
    2853      * Setup the recursion.
    2854      */
    2855     int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
    2856     if (RT_SUCCESS(rc))
    2857     {
    2858         RTDIRENTRY Entry;
    2859         rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
    2860     }
    2861     else
    2862         RTMsgError("RTPathAppend: %Rrc\n", rc);
    2863     return rc;
    2864 }
    2865 
    2866 
    2867 /**
    2868  * Processes a file or directory specified as an command line argument.
    2869  *
    2870  * @returns IPRT status code
    2871  * @param   pszSomething        What we found in the command line arguments.
    2872  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2873  */
    2874 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
    2875 {
    2876     char szBuf[RTPATH_MAX];
    2877     int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
    2878     if (RT_SUCCESS(rc))
    2879     {
    2880         RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
    2881 
    2882         PSCMSETTINGS pSettings;
    2883         rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
    2884         if (RT_SUCCESS(rc))
    2885         {
    2886             scmSettingsStackPush(&pSettingsStack, pSettings);
    2887 
    2888             if (RTFileExists(szBuf))
    2889             {
    2890                 const char *pszBasename = RTPathFilename(szBuf);
    2891                 if (pszBasename)
    2892                 {
    2893                     size_t cchBasename = strlen(pszBasename);
    2894                     rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
    2895                 }
    2896                 else
    2897                 {
    2898                     RTMsgError("RTPathFilename: NULL\n");
    2899                     rc = VERR_IS_A_DIRECTORY;
    2900                 }
    2901             }
    2902             else
    2903                 rc = scmProcessDirTree(szBuf, pSettingsStack);
    2904 
    2905             PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
    2906             Assert(pPopped == pSettings);
    2907             scmSettingsDestroy(pSettings);
    2908         }
    2909         else
    2910             RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
    2911     }
    2912     else
    2913         RTMsgError("RTPathAbs: %Rrc\n", rc);
    2914     return rc;
    2915 }
    2916 
    2917 int main(int argc, char **argv)
    2918 {
    2919     int rc = RTR3InitExe(argc, &argv, 0);
    2920     if (RT_FAILURE(rc))
    2921         return 1;
    2922 
    2923     /*
    2924      * Init the settings.
    2925      */
    2926     PSCMSETTINGS pSettings;
    2927     rc = scmSettingsCreate(&pSettings, &g_Defaults);
    2928     if (RT_FAILURE(rc))
    2929     {
    2930         RTMsgError("scmSettingsCreate: %Rrc\n", rc);
    2931         return 1;
    2932     }
    2933 
    2934     /*
    2935      * Parse arguments and process input in order (because this is the only
    2936      * thing that works at the moment).
    2937      */
    2938     static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
    2939     {
    2940         { "--dry-run",                          'd',                                    RTGETOPT_REQ_NOTHING },
    2941         { "--real-run",                         'D',                                    RTGETOPT_REQ_NOTHING },
    2942         { "--file-filter",                      'f',                                    RTGETOPT_REQ_STRING  },
    2943         { "--quiet",                            'q',                                    RTGETOPT_REQ_NOTHING },
    2944         { "--verbose",                          'v',                                    RTGETOPT_REQ_NOTHING },
    2945         { "--diff-ignore-eol",                  SCMOPT_DIFF_IGNORE_EOL,                 RTGETOPT_REQ_NOTHING },
    2946         { "--diff-no-ignore-eol",               SCMOPT_DIFF_NO_IGNORE_EOL,              RTGETOPT_REQ_NOTHING },
    2947         { "--diff-ignore-space",                SCMOPT_DIFF_IGNORE_SPACE,               RTGETOPT_REQ_NOTHING },
    2948         { "--diff-no-ignore-space",             SCMOPT_DIFF_NO_IGNORE_SPACE,            RTGETOPT_REQ_NOTHING },
    2949         { "--diff-ignore-leading-space",        SCMOPT_DIFF_IGNORE_LEADING_SPACE,       RTGETOPT_REQ_NOTHING },
    2950         { "--diff-no-ignore-leading-space",     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,    RTGETOPT_REQ_NOTHING },
    2951         { "--diff-ignore-trailing-space",       SCMOPT_DIFF_IGNORE_TRAILING_SPACE,      RTGETOPT_REQ_NOTHING },
    2952         { "--diff-no-ignore-trailing-space",    SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,   RTGETOPT_REQ_NOTHING },
    2953         { "--diff-special-chars",               SCMOPT_DIFF_SPECIAL_CHARS,              RTGETOPT_REQ_NOTHING },
    2954         { "--diff-no-special-chars",            SCMOPT_DIFF_NO_SPECIAL_CHARS,           RTGETOPT_REQ_NOTHING },
    2955     };
    2956     memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
    2957 
    2958     RTGETOPTUNION   ValueUnion;
    2959     RTGETOPTSTATE   GetOptState;
    2960     rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
    2961     AssertReleaseRCReturn(rc, 1);
    2962     size_t          cProcessed = 0;
    2963 
    2964     while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    2965     {
    2966         switch (rc)
    2967         {
    2968             case 'd':
    2969                 g_fDryRun = true;
    2970                 break;
    2971             case 'D':
    2972                 g_fDryRun = false;
    2973                 break;
    2974 
    2975             case 'f':
    2976                 g_pszFileFilter = ValueUnion.psz;
    2977                 break;
    2978 
    2979             case 'h':
    2980                 RTPrintf("VirtualBox Source Code Massager\n"
    2981                          "\n"
    2982                          "Usage: %s [options] <files & dirs>\n"
    2983                          "\n"
    2984                          "Options:\n", g_szProgName);
    2985                 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
    2986                 {
    2987                     bool fAdvanceTwo = false;
    2988                     if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
    2989                     {
    2990                         fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
    2991                                    && (   strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
    2992                                        || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
    2993                                        || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
    2994                                       );
    2995                         if (fAdvanceTwo)
    2996                             RTPrintf("  %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
    2997                         else
    2998                             RTPrintf("  %s\n", s_aOpts[i].pszLong);
    2999                     }
    3000                     else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
    3001                         RTPrintf("  %s string\n", s_aOpts[i].pszLong);
    3002                     else
    3003                         RTPrintf("  %s value\n", s_aOpts[i].pszLong);
    3004                     switch (s_aOpts[i].iShort)
    3005                     {
    3006                         case SCMOPT_CONVERT_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertEol); break;
    3007                         case SCMOPT_CONVERT_TABS:           RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertTabs); break;
    3008                         case SCMOPT_FORCE_FINAL_EOL:        RTPrintf("      Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
    3009                         case SCMOPT_FORCE_TRAILING_LINE:    RTPrintf("      Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
    3010                         case SCMOPT_STRIP_TRAILING_BLANKS:  RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
    3011                         case SCMOPT_STRIP_TRAILING_LINES:   RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
    3012                         case SCMOPT_ONLY_SVN_DIRS:          RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
    3013                         case SCMOPT_ONLY_SVN_FILES:         RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
    3014                         case SCMOPT_SET_SVN_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
    3015                         case SCMOPT_SET_SVN_EXECUTABLE:     RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
    3016                         case SCMOPT_SET_SVN_KEYWORDS:       RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
    3017                         case SCMOPT_TAB_SIZE:               RTPrintf("      Default: %u\n", g_Defaults.cchTab); break;
    3018                         case SCMOPT_FILTER_OUT_DIRS:        RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutDirs); break;
    3019                         case SCMOPT_FILTER_FILES:           RTPrintf("      Default: %s\n", g_Defaults.pszFilterFiles); break;
    3020                         case SCMOPT_FILTER_OUT_FILES:       RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutFiles); break;
    3021                     }
    3022                     i += fAdvanceTwo;
    3023                 }
    3024                 return 1;
    3025 
    3026             case 'q':
    3027                 g_iVerbosity = 0;
    3028                 break;
    3029 
    3030             case 'v':
    3031                 g_iVerbosity++;
    3032                 break;
    3033 
    3034             case 'V':
    3035             {
    3036                 /* The following is assuming that svn does it's job here. */
    3037                 static const char s_szRev[] = "$Revision$";
    3038                 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
    3039                 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
    3040                 return 0;
    3041             }
    3042 
    3043             case SCMOPT_DIFF_IGNORE_EOL:
    3044                 g_fDiffIgnoreEol = true;
    3045                 break;
    3046             case SCMOPT_DIFF_NO_IGNORE_EOL:
    3047                 g_fDiffIgnoreEol = false;
    3048                 break;
    3049 
    3050             case SCMOPT_DIFF_IGNORE_SPACE:
    3051                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
    3052                 break;
    3053             case SCMOPT_DIFF_NO_IGNORE_SPACE:
    3054                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
    3055                 break;
    3056 
    3057             case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
    3058                 g_fDiffIgnoreLeadingWS = true;
    3059                 break;
    3060             case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
    3061                 g_fDiffIgnoreLeadingWS = false;
    3062                 break;
    3063 
    3064             case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
    3065                 g_fDiffIgnoreTrailingWS = true;
    3066                 break;
    3067             case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
    3068                 g_fDiffIgnoreTrailingWS = false;
    3069                 break;
    3070 
    3071             case SCMOPT_DIFF_SPECIAL_CHARS:
    3072                 g_fDiffSpecialChars = true;
    3073                 break;
    3074             case SCMOPT_DIFF_NO_SPECIAL_CHARS:
    3075                 g_fDiffSpecialChars = false;
    3076                 break;
    3077 
    3078             case VINF_GETOPT_NOT_OPTION:
    3079             {
    3080                 if (!g_fDryRun)
    3081                 {
    3082                     if (!cProcessed)
    3083                     {
    3084                         RTPrintf("%s: Warning! This program will make changes to your source files and\n"
    3085                                  "%s:          there is a slight risk that bugs or a full disk may cause\n"
    3086                                  "%s:          LOSS OF DATA.   So, please make sure you have checked in\n"
    3087                                  "%s:          all your changes already.  If you didn't, then don't blame\n"
    3088                                  "%s:          anyone for not warning you!\n"
    3089                                  "%s:\n"
    3090                                  "%s:          Press any key to continue...\n",
    3091                                  g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
    3092                                  g_szProgName, g_szProgName);
    3093                         RTStrmGetCh(g_pStdIn);
    3094                     }
    3095                     cProcessed++;
    3096                 }
    3097                 rc = scmProcessSomething(ValueUnion.psz, pSettings);
    3098                 if (RT_FAILURE(rc))
    3099                     return rc;
    3100                 break;
    3101             }
    3102 
    3103             default:
    3104             {
    3105                 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
    3106                 if (RT_SUCCESS(rc2))
    3107                     break;
    3108                 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
    3109                     return 2;
    3110                 return RTGetOptPrintError(rc, &ValueUnion);
    3111             }
    3112         }
    3113     }
    3114 
    3115     scmSettingsDestroy(pSettings);
    3116     return 0;
    3117 }
    3118 
  • trunk/src/bldprogs/scmdiff.h

    r40528 r40530  
    11/* $Id$ */
    22/** @file
    3  * IPRT Testcase / Tool - Source Code Massager.
     3 * IPRT Testcase / Tool - Source Code Massager Diff Code.
    44 */
    55
     
    1616 */
    1717
    18 /*******************************************************************************
    19 *   Header Files                                                               *
    20 *******************************************************************************/
    21 #include <iprt/assert.h>
    22 #include <iprt/ctype.h>
    23 #include <iprt/dir.h>
    24 #include <iprt/env.h>
    25 #include <iprt/file.h>
    26 #include <iprt/err.h>
    27 #include <iprt/getopt.h>
    28 #include <iprt/initterm.h>
    29 #include <iprt/mem.h>
    30 #include <iprt/message.h>
    31 #include <iprt/param.h>
    32 #include <iprt/path.h>
    33 #include <iprt/process.h>
     18#ifndef ___scmdiff_h___
     19#define ___scmdiff_h___
     20
    3421#include <iprt/stream.h>
    35 #include <iprt/string.h>
    36 
    3722#include "scmstream.h"
    3823
    39 
    40 /*******************************************************************************
    41 *   Defined Constants And Macros                                               *
    42 *******************************************************************************/
    43 /** The name of the settings files. */
    44 #define SCM_SETTINGS_FILENAME           ".scm-settings"
    45 
    46 
    47 /*******************************************************************************
    48 *   Structures and Typedefs                                                    *
    49 *******************************************************************************/
    50 /** Pointer to const massager settings. */
    51 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
    52 
    53 /**
    54  * SVN property.
    55  */
    56 typedef struct SCMSVNPROP
    57 {
    58     /** The property. */
    59     char           *pszName;
    60     /** The value.
    61      * When used to record updates, this can be set to NULL to trigger the
    62      * deletion of the property. */
    63     char           *pszValue;
    64 } SCMSVNPROP;
    65 /** Pointer to a SVN property. */
    66 typedef SCMSVNPROP *PSCMSVNPROP;
    67 /** Pointer to a const  SVN property. */
    68 typedef SCMSVNPROP const *PCSCMSVNPROP;
    69 
    70 
    71 /**
    72  * Rewriter state.
    73  */
    74 typedef struct SCMRWSTATE
    75 {
    76     /** The filename.  */
    77     const char     *pszFilename;
    78     /** Set after the printing the first verbose message about a file under
    79      *  rewrite. */
    80     bool            fFirst;
    81     /** The number of SVN property changes. */
    82     size_t          cSvnPropChanges;
    83     /** Pointer to an array of SVN property changes. */
    84     PSCMSVNPROP     paSvnPropChanges;
    85 } SCMRWSTATE;
    86 /** Pointer to the rewriter state. */
    87 typedef SCMRWSTATE *PSCMRWSTATE;
    88 
    89 /**
    90  * A rewriter.
    91  *
    92  * This works like a stream editor, reading @a pIn, modifying it and writing it
    93  * to @a pOut.
    94  *
    95  * @returns true if any changes were made, false if not.
    96  * @param   pIn                 The input stream.
    97  * @param   pOut                The output stream.
    98  * @param   pSettings           The settings.
    99  */
    100 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    101 
    102 
    103 /**
    104  * Configuration entry.
    105  */
    106 typedef struct SCMCFGENTRY
    107 {
    108     /** Number of rewriters. */
    109     size_t          cRewriters;
    110     /** Pointer to an array of rewriters. */
    111     PFNSCMREWRITER const  *papfnRewriter;
    112     /** File pattern (simple).  */
    113     const char     *pszFilePattern;
    114 } SCMCFGENTRY;
    115 typedef SCMCFGENTRY *PSCMCFGENTRY;
    116 typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    117 
     24RT_C_DECLS_BEGIN
    11825
    11926/**
     
    14451typedef SCMDIFFSTATE *PSCMDIFFSTATE;
    14552
    146 /**
    147  * Source Code Massager Settings.
    148  */
    149 typedef struct SCMSETTINGSBASE
    150 {
    151     bool            fConvertEol;
    152     bool            fConvertTabs;
    153     bool            fForceFinalEol;
    154     bool            fForceTrailingLine;
    155     bool            fStripTrailingBlanks;
    156     bool            fStripTrailingLines;
    157     /** Only process files that are part of a SVN working copy. */
    158     bool            fOnlySvnFiles;
    159     /** Only recurse into directories containing an .svn dir.  */
    160     bool            fOnlySvnDirs;
    161     /** Set svn:eol-style if missing or incorrect. */
    162     bool            fSetSvnEol;
    163     /** Set svn:executable according to type (unusually this means deleting it). */
    164     bool            fSetSvnExecutable;
    165     /** Set svn:keyword if completely or partially missing. */
    166     bool            fSetSvnKeywords;
    167     /**  */
    168     unsigned        cchTab;
    169     /** Only consider files matching these patterns.  This is only applied to the
    170      *  base names. */
    171     char           *pszFilterFiles;
    172     /** Filter out files matching the following patterns.  This is applied to base
    173      *  names as well as the absolute paths.  */
    174     char           *pszFilterOutFiles;
    175     /** Filter out directories matching the following patterns.  This is applied
    176      *  to base names as well as the absolute paths.  All absolute paths ends with a
    177      *  slash and dot ("/.").  */
    178     char           *pszFilterOutDirs;
    179 } SCMSETTINGSBASE;
    180 /** Pointer to massager settings. */
    181 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
    18253
    183 /**
    184  * Option identifiers.
    185  *
    186  * @note    The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
    187  *          clear.  So, the option setting a flag (boolean) will have an even
    188  *          number and the one clearing it will have an odd number.
    189  * @note    Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
    190  */
    191 typedef enum SCMOPT
    192 {
    193     SCMOPT_CONVERT_EOL = 10000,
    194     SCMOPT_NO_CONVERT_EOL,
    195     SCMOPT_CONVERT_TABS,
    196     SCMOPT_NO_CONVERT_TABS,
    197     SCMOPT_FORCE_FINAL_EOL,
    198     SCMOPT_NO_FORCE_FINAL_EOL,
    199     SCMOPT_FORCE_TRAILING_LINE,
    200     SCMOPT_NO_FORCE_TRAILING_LINE,
    201     SCMOPT_STRIP_TRAILING_BLANKS,
    202     SCMOPT_NO_STRIP_TRAILING_BLANKS,
    203     SCMOPT_STRIP_TRAILING_LINES,
    204     SCMOPT_NO_STRIP_TRAILING_LINES,
    205     SCMOPT_ONLY_SVN_DIRS,
    206     SCMOPT_NOT_ONLY_SVN_DIRS,
    207     SCMOPT_ONLY_SVN_FILES,
    208     SCMOPT_NOT_ONLY_SVN_FILES,
    209     SCMOPT_SET_SVN_EOL,
    210     SCMOPT_DONT_SET_SVN_EOL,
    211     SCMOPT_SET_SVN_EXECUTABLE,
    212     SCMOPT_DONT_SET_SVN_EXECUTABLE,
    213     SCMOPT_SET_SVN_KEYWORDS,
    214     SCMOPT_DONT_SET_SVN_KEYWORDS,
    215     SCMOPT_TAB_SIZE,
    216     SCMOPT_FILTER_OUT_DIRS,
    217     SCMOPT_FILTER_FILES,
    218     SCMOPT_FILTER_OUT_FILES,
    219     SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
    220     //
    221     SCMOPT_DIFF_IGNORE_EOL,
    222     SCMOPT_DIFF_NO_IGNORE_EOL,
    223     SCMOPT_DIFF_IGNORE_SPACE,
    224     SCMOPT_DIFF_NO_IGNORE_SPACE,
    225     SCMOPT_DIFF_IGNORE_LEADING_SPACE,
    226     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
    227     SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
    228     SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
    229     SCMOPT_DIFF_SPECIAL_CHARS,
    230     SCMOPT_DIFF_NO_SPECIAL_CHARS,
    231     SCMOPT_END
    232 } SCMOPT;
    233 
    234 
    235 /**
    236  * File/dir pattern + options.
    237  */
    238 typedef struct SCMPATRNOPTPAIR
    239 {
    240     char *pszPattern;
    241     char *pszOptions;
    242 } SCMPATRNOPTPAIR;
    243 /** Pointer to a pattern + option pair. */
    244 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
    245 
    246 
    247 /** Pointer to a settings set. */
    248 typedef struct SCMSETTINGS *PSCMSETTINGS;
    249 /**
    250  * Settings set.
    251  *
    252  * This structure is constructed from the command line arguments or any
    253  * .scm-settings file found in a directory we recurse into.  When recursing in
    254  * and out of a directory, we push and pop a settings set for it.
    255  *
    256  * The .scm-settings file has two kinds of setttings, first there are the
    257  * unqualified base settings and then there are the settings which applies to a
    258  * set of files or directories.  The former are lines with command line options.
    259  * For the latter, the options are preceded by a string pattern and a colon.
    260  * The pattern specifies which files (and/or directories) the options applies
    261  * to.
    262  *
    263  * We parse the base options into the Base member and put the others into the
    264  * paPairs array.
    265  */
    266 typedef struct SCMSETTINGS
    267 {
    268     /** Pointer to the setting file below us in the stack. */
    269     PSCMSETTINGS        pDown;
    270     /** Pointer to the setting file above us in the stack. */
    271     PSCMSETTINGS        pUp;
    272     /** File/dir patterns and their options. */
    273     PSCMPATRNOPTPAIR    paPairs;
    274     /** The number of entires in paPairs. */
    275     uint32_t            cPairs;
    276     /** The base settings that was read out of the file. */
    277     SCMSETTINGSBASE     Base;
    278 } SCMSETTINGS;
    279 /** Pointer to a const settings set. */
    280 typedef SCMSETTINGS const *PCSCMSETTINGS;
    281 
    282 
    283 /*******************************************************************************
    284 *   Internal Functions                                                         *
    285 *******************************************************************************/
    286 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    287 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    288 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    289 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    290 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    291 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    292 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    293 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    294 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    295 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    296 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    297 
    298 
    299 /*******************************************************************************
    300 *   Global Variables                                                           *
    301 *******************************************************************************/
    302 static const char   g_szProgName[]          = "scm";
    303 static const char  *g_pszChangedSuff        = "";
    304 static const char   g_szTabSpaces[16+1]     = "                ";
    305 static bool         g_fDryRun               = true;
    306 static bool         g_fDiffSpecialChars     = true;
    307 static bool         g_fDiffIgnoreEol        = false;
    308 static bool         g_fDiffIgnoreLeadingWS  = false;
    309 static bool         g_fDiffIgnoreTrailingWS = false;
    310 static int          g_iVerbosity            = 2;//99; //0;
    311 
    312 /** The global settings. */
    313 static SCMSETTINGSBASE const g_Defaults =
    314 {
    315     /* .fConvertEol = */            true,
    316     /* .fConvertTabs = */           true,
    317     /* .fForceFinalEol = */         true,
    318     /* .fForceTrailingLine = */     false,
    319     /* .fStripTrailingBlanks = */   true,
    320     /* .fStripTrailingLines = */    true,
    321     /* .fOnlySvnFiles = */          false,
    322     /* .fOnlySvnDirs = */           false,
    323     /* .fSetSvnEol = */             false,
    324     /* .fSetSvnExecutable = */      false,
    325     /* .fSetSvnKeywords = */        false,
    326     /* .cchTab = */                 8,
    327     /* .pszFilterFiles = */         (char *)"",
    328     /* .pszFilterOutFiles = */      (char *)"*.exe|*.com|20*-*-*.log",
    329     /* .pszFilterOutDirs = */       (char *)".svn|.hg|.git|CVS",
    330 };
    331 
    332 /** Option definitions for the base settings. */
    333 static RTGETOPTDEF  g_aScmOpts[] =
    334 {
    335     { "--convert-eol",                      SCMOPT_CONVERT_EOL,                     RTGETOPT_REQ_NOTHING },
    336     { "--no-convert-eol",                   SCMOPT_NO_CONVERT_EOL,                  RTGETOPT_REQ_NOTHING },
    337     { "--convert-tabs",                     SCMOPT_CONVERT_TABS,                    RTGETOPT_REQ_NOTHING },
    338     { "--no-convert-tabs",                  SCMOPT_NO_CONVERT_TABS,                 RTGETOPT_REQ_NOTHING },
    339     { "--force-final-eol",                  SCMOPT_FORCE_FINAL_EOL,                 RTGETOPT_REQ_NOTHING },
    340     { "--no-force-final-eol",               SCMOPT_NO_FORCE_FINAL_EOL,              RTGETOPT_REQ_NOTHING },
    341     { "--force-trailing-line",              SCMOPT_FORCE_TRAILING_LINE,             RTGETOPT_REQ_NOTHING },
    342     { "--no-force-trailing-line",           SCMOPT_NO_FORCE_TRAILING_LINE,          RTGETOPT_REQ_NOTHING },
    343     { "--strip-trailing-blanks",            SCMOPT_STRIP_TRAILING_BLANKS,           RTGETOPT_REQ_NOTHING },
    344     { "--no-strip-trailing-blanks",         SCMOPT_NO_STRIP_TRAILING_BLANKS,        RTGETOPT_REQ_NOTHING },
    345     { "--strip-trailing-lines",             SCMOPT_STRIP_TRAILING_LINES,            RTGETOPT_REQ_NOTHING },
    346     { "--strip-no-trailing-lines",          SCMOPT_NO_STRIP_TRAILING_LINES,         RTGETOPT_REQ_NOTHING },
    347     { "--only-svn-dirs",                    SCMOPT_ONLY_SVN_DIRS,                   RTGETOPT_REQ_NOTHING },
    348     { "--not-only-svn-dirs",                SCMOPT_NOT_ONLY_SVN_DIRS,               RTGETOPT_REQ_NOTHING },
    349     { "--only-svn-files",                   SCMOPT_ONLY_SVN_FILES,                  RTGETOPT_REQ_NOTHING },
    350     { "--not-only-svn-files",               SCMOPT_NOT_ONLY_SVN_FILES,              RTGETOPT_REQ_NOTHING },
    351     { "--set-svn-eol",                      SCMOPT_SET_SVN_EOL,                     RTGETOPT_REQ_NOTHING },
    352     { "--dont-set-svn-eol",                 SCMOPT_DONT_SET_SVN_EOL,                RTGETOPT_REQ_NOTHING },
    353     { "--set-svn-executable",               SCMOPT_SET_SVN_EXECUTABLE,              RTGETOPT_REQ_NOTHING },
    354     { "--dont-set-svn-executable",          SCMOPT_DONT_SET_SVN_EXECUTABLE,         RTGETOPT_REQ_NOTHING },
    355     { "--set-svn-keywords",                 SCMOPT_SET_SVN_KEYWORDS,                RTGETOPT_REQ_NOTHING },
    356     { "--dont-set-svn-keywords",            SCMOPT_DONT_SET_SVN_KEYWORDS,           RTGETOPT_REQ_NOTHING },
    357     { "--tab-size",                         SCMOPT_TAB_SIZE,                        RTGETOPT_REQ_UINT8   },
    358     { "--filter-out-dirs",                  SCMOPT_FILTER_OUT_DIRS,                 RTGETOPT_REQ_STRING  },
    359     { "--filter-files",                     SCMOPT_FILTER_FILES,                    RTGETOPT_REQ_STRING  },
    360     { "--filter-out-files",                 SCMOPT_FILTER_OUT_FILES,                RTGETOPT_REQ_STRING  },
    361 };
    362 
    363 /** Consider files matching the following patterns (base names only). */
    364 static const char  *g_pszFileFilter         = NULL;
    365 
    366 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
    367 {
    368     rewrite_SvnNoExecutable,
    369     rewrite_Makefile_kup
    370 };
    371 
    372 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
    373 {
    374     rewrite_ForceNativeEol,
    375     rewrite_StripTrailingBlanks,
    376     rewrite_AdjustTrailingLines,
    377     rewrite_SvnNoExecutable,
    378     rewrite_SvnKeywords,
    379     rewrite_Makefile_kmk
    380 };
    381 
    382 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
    383 {
    384     rewrite_ForceNativeEol,
    385     rewrite_ExpandTabs,
    386     rewrite_StripTrailingBlanks,
    387     rewrite_AdjustTrailingLines,
    388     rewrite_SvnNoExecutable,
    389     rewrite_SvnKeywords,
    390     rewrite_C_and_CPP
    391 };
    392 
    393 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
    394 {
    395     rewrite_ForceNativeEol,
    396     rewrite_ExpandTabs,
    397     rewrite_StripTrailingBlanks,
    398     rewrite_AdjustTrailingLines,
    399     rewrite_SvnNoExecutable,
    400     rewrite_C_and_CPP
    401 };
    402 
    403 static PFNSCMREWRITER const g_aRewritersFor_RC[] =
    404 {
    405     rewrite_ForceNativeEol,
    406     rewrite_ExpandTabs,
    407     rewrite_StripTrailingBlanks,
    408     rewrite_AdjustTrailingLines,
    409     rewrite_SvnNoExecutable,
    410     rewrite_SvnKeywords
    411 };
    412 
    413 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
    414 {
    415     rewrite_ForceLF,
    416     rewrite_ExpandTabs,
    417     rewrite_StripTrailingBlanks
    418 };
    419 
    420 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
    421 {
    422     rewrite_ForceCRLF,
    423     rewrite_ExpandTabs,
    424     rewrite_StripTrailingBlanks
    425 };
    426 
    427 static SCMCFGENTRY const g_aConfigs[] =
    428 {
    429     { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
    430     { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
    431     { RT_ELEMENTS(g_aRewritersFor_C_and_CPP),    &g_aRewritersFor_C_and_CPP[0],    "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
    432     { RT_ELEMENTS(g_aRewritersFor_H_and_HPP),    &g_aRewritersFor_H_and_HPP[0],    "*.h|*.hpp" },
    433     { RT_ELEMENTS(g_aRewritersFor_RC),           &g_aRewritersFor_RC[0],           "*.rc" },
    434     { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
    435     { RT_ELEMENTS(g_aRewritersFor_BatchFiles),   &g_aRewritersFor_BatchFiles[0],   "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
    436 };
    437 
    438 
    439 /* -=-=-=-=-=- diff -=-=-=-=-=- */
    440 
    441 
    442 /**
    443  * Prints a range of lines with a prefix.
    444  *
    445  * @param   pState              The diff state.
    446  * @param   chPrefix            The prefix.
    447  * @param   pStream             The stream to get the lines from.
    448  * @param   iLine               The first line.
    449  * @param   cLines              The number of lines.
    450  */
    451 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
    452 {
    453     while (cLines-- > 0)
    454     {
    455         SCMEOL      enmEol;
    456         size_t      cchLine;
    457         const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    458 
    459         RTStrmPutCh(pState->pDiff, chPrefix);
    460         if (pchLine && cchLine)
    461         {
    462             if (!pState->fSpecialChars)
    463                 RTStrmWrite(pState->pDiff, pchLine, cchLine);
    464             else
    465             {
    466                 size_t      offVir   = 0;
    467                 const char *pchStart = pchLine;
    468                 const char *pchTab   = (const char *)memchr(pchLine, '\t', cchLine);
    469                 while (pchTab)
    470                 {
    471                     RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
    472                     offVir += pchTab - pchStart;
    473 
    474                     size_t cchTab = pState->cchTab - offVir % pState->cchTab;
    475                     switch (cchTab)
    476                     {
    477                         case 1: RTStrmPutStr(pState->pDiff, "."); break;
    478                         case 2: RTStrmPutStr(pState->pDiff, ".."); break;
    479                         case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
    480                         case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
    481                         case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
    482                         default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
    483                     }
    484                     offVir += cchTab;
    485 
    486                     /* next */
    487                     pchStart = pchTab + 1;
    488                     pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
    489                 }
    490                 size_t cchLeft = cchLine - (pchStart - pchLine);
    491                 if (cchLeft)
    492                     RTStrmWrite(pState->pDiff, pchStart, cchLeft);
    493             }
    494         }
    495 
    496         if (!pState->fSpecialChars)
    497             RTStrmPutCh(pState->pDiff, '\n');
    498         else if (enmEol == SCMEOL_LF)
    499             RTStrmPutStr(pState->pDiff, "[LF]\n");
    500         else if (enmEol == SCMEOL_CRLF)
    501             RTStrmPutStr(pState->pDiff, "[CRLF]\n");
    502         else
    503             RTStrmPutStr(pState->pDiff, "[NONE]\n");
    504 
    505         iLine++;
    506     }
    507 }
    508 
    509 
    510 /**
    511  * Reports a difference and propels the streams to the lines following the
    512  * resync.
    513  *
    514  *
    515  * @returns New pState->cDiff value (just to return something).
    516  * @param   pState              The diff state.  The cDiffs member will be
    517  *                              incremented.
    518  * @param   cMatches            The resync length.
    519  * @param   iLeft               Where the difference starts on the left side.
    520  * @param   cLeft               How long it is on this side.  ~(size_t)0 is used
    521  *                              to indicate that it goes all the way to the end.
    522  * @param   iRight              Where the difference starts on the right side.
    523  * @param   cRight              How long it is.
    524  */
    525 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
    526                             size_t iLeft, size_t cLeft,
    527                             size_t iRight, size_t cRight)
    528 {
    529     /*
    530      * Adjust the input.
    531      */
    532     if (cLeft == ~(size_t)0)
    533     {
    534         size_t c = ScmStreamCountLines(pState->pLeft);
    535         if (c >= iLeft)
    536             cLeft = c - iLeft;
    537         else
    538         {
    539             iLeft = c;
    540             cLeft = 0;
    541         }
    542     }
    543 
    544     if (cRight == ~(size_t)0)
    545     {
    546         size_t c = ScmStreamCountLines(pState->pRight);
    547         if (c >= iRight)
    548             cRight = c - iRight;
    549         else
    550         {
    551             iRight = c;
    552             cRight = 0;
    553         }
    554     }
    555 
    556     /*
    557      * Print header if it's the first difference
    558      */
    559     if (!pState->cDiffs)
    560         RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
    561 
    562     /*
    563      * Emit the change description.
    564      */
    565     char ch = cLeft == 0
    566             ? 'a'
    567             : cRight == 0
    568             ? 'd'
    569             : 'c';
    570     if (cLeft > 1 && cRight > 1)
    571         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
    572     else if (cLeft > 1)
    573         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n",     iLeft + 1, iLeft + cLeft, ch, iRight + 1);
    574     else if (cRight > 1)
    575         RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n",     iLeft + 1,                ch, iRight + 1, iRight + cRight);
    576     else
    577         RTStrmPrintf(pState->pDiff, "%zu%c%zu\n",         iLeft + 1,                ch, iRight + 1);
    578 
    579     /*
    580      * And the lines.
    581      */
    582     if (cLeft)
    583         scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
    584     if (cLeft && cRight)
    585         RTStrmPrintf(pState->pDiff, "---\n");
    586     if (cRight)
    587         scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
    588 
    589     /*
    590      * Reposition the streams (safely ignores return value).
    591      */
    592     ScmStreamSeekByLine(pState->pLeft,  iLeft  + cLeft  + cMatches);
    593     ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
    594 
    595     pState->cDiffs++;
    596     return pState->cDiffs;
    597 }
    598 
    599 /**
    600  * Helper for scmDiffCompare that takes care of trailing spaces and stuff
    601  * like that.
    602  */
    603 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
    604                                const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    605                                const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    606 {
    607     if (pState->fIgnoreTrailingWhite)
    608     {
    609         while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
    610             cchLeft--;
    611         while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
    612             cchRight--;
    613     }
    614 
    615     if (pState->fIgnoreLeadingWhite)
    616     {
    617         while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
    618             pchLeft++, cchLeft--;
    619         while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
    620             pchRight++, cchRight--;
    621     }
    622 
    623     if (   cchLeft != cchRight
    624         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    625         || memcmp(pchLeft, pchRight, cchLeft))
    626         return false;
    627     return true;
    628 }
    629 
    630 /**
    631  * Compare two lines.
    632  *
    633  * @returns true if the are equal, false if not.
    634  */
    635 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
    636                                 const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    637                                 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    638 {
    639     if (   cchLeft != cchRight
    640         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    641         || memcmp(pchLeft, pchRight, cchLeft))
    642     {
    643         if (   pState->fIgnoreTrailingWhite
    644             || pState->fIgnoreTrailingWhite)
    645             return scmDiffCompareSlow(pState,
    646                                       pchLeft, cchLeft, enmEolLeft,
    647                                       pchRight, cchRight, enmEolRight);
    648         return false;
    649     }
    650     return true;
    651 }
    652 
    653 /**
    654  * Compares two sets of lines from the two files.
    655  *
    656  * @returns true if they matches, false if they don't.
    657  * @param   pState              The diff state.
    658  * @param   iLeft               Where to start in the left stream.
    659  * @param   iRight              Where to start in the right stream.
    660  * @param   cLines              How many lines to compare.
    661  */
    662 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
    663 {
    664     for (size_t iLine = 0; iLine < cLines; iLine++)
    665     {
    666         SCMEOL      enmEolLeft;
    667         size_t      cchLeft;
    668         const char *pchLeft  = ScmStreamGetLineByNo(pState->pLeft,  iLeft + iLine,  &cchLeft,  &enmEolLeft);
    669 
    670         SCMEOL      enmEolRight;
    671         size_t      cchRight;
    672         const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
    673 
    674         if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    675             return false;
    676     }
    677     return true;
    678 }
    679 
    680 
    681 /**
    682  * Resynchronize the two streams and reports the difference.
    683  *
    684  * Upon return, the streams will be positioned after the block of @a cMatches
    685  * lines where it resynchronized them.
    686  *
    687  * @returns pState->cDiffs (just so we can use it in a return statement).
    688  * @param   pState              The state.
    689  * @param   cMatches            The number of lines that needs to match for the
    690  *                              stream to be considered synchronized again.
    691  */
    692 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
    693 {
    694     size_t const iStartLeft  = ScmStreamTellLine(pState->pLeft)  - 1;
    695     size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
    696     Assert(cMatches > 0);
    697 
    698     /*
    699      * Compare each new line from each of the streams will all the preceding
    700      * ones, including iStartLeft/Right.
    701      */
    702     for (size_t iRange = 1; ; iRange++)
    703     {
    704         /*
    705          * Get the next line in the left stream and compare it against all the
    706          * preceding lines on the right side.
    707          */
    708         SCMEOL      enmEol;
    709         size_t      cchLine;
    710         const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
    711         if (!pchLine)
    712             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    713 
    714         for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
    715         {
    716             SCMEOL      enmEolRight;
    717             size_t      cchRight;
    718             const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
    719                                                         &cchRight, &enmEolRight);
    720             if (   scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
    721                 && scmDiffCompareLines(pState,
    722                                        iStartLeft  + iRange + 1 - cMatches,
    723                                        iStartRight + iRight + 1 - cMatches,
    724                                        cMatches - 1)
    725                )
    726                 return scmDiffReport(pState, cMatches,
    727                                      iStartLeft,  iRange + 1 - cMatches,
    728                                      iStartRight, iRight + 1 - cMatches);
    729         }
    730 
    731         /*
    732          * Get the next line in the right stream and compare it against all the
    733          * lines on the right side.
    734          */
    735         pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
    736         if (!pchLine)
    737             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    738 
    739         for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
    740         {
    741             SCMEOL      enmEolLeft;
    742             size_t      cchLeft;
    743             const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
    744                                                        &cchLeft, &enmEolLeft);
    745             if (    scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
    746                 && scmDiffCompareLines(pState,
    747                                        iStartLeft  + iLeft  + 1 - cMatches,
    748                                        iStartRight + iRange + 1 - cMatches,
    749                                        cMatches - 1)
    750                )
    751                 return scmDiffReport(pState, cMatches,
    752                                      iStartLeft,  iLeft  + 1 - cMatches,
    753                                      iStartRight, iRange + 1 - cMatches);
    754         }
    755     }
    756 }
    757 
    758 /**
    759  * Creates a diff of the changes between the streams @a pLeft and @a pRight.
    760  *
    761  * This currently only implements the simplest diff format, so no contexts.
    762  *
    763  * Also, note that we won't detect differences in the final newline of the
    764  * streams.
    765  *
    766  * @returns The number of differences.
    767  * @param   pszFilename         The filename.
    768  * @param   pLeft               The left side stream.
    769  * @param   pRight              The right side stream.
    770  * @param   fIgnoreEol          Whether to ignore end of line markers.
    771  * @param   fIgnoreLeadingWhite Set if leading white space should be ignored.
    772  * @param   fIgnoreTrailingWhite  Set if trailing white space should be ignored.
    773  * @param   fSpecialChars       Whether to print special chars in a human
    774  *                              readable form or not.
    775  * @param   cchTab              The tab size.
    776  * @param   pDiff               Where to write the diff.
    777  */
    77854size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
    77955                      bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
    780                       size_t cchTab, PRTSTREAM pDiff)
    781 {
    782 #ifdef RT_STRICT
    783     ScmStreamCheckItegrity(pLeft);
    784     ScmStreamCheckItegrity(pRight);
     56                      size_t cchTab, PRTSTREAM pDiff);
     57
     58RT_C_DECLS_END
     59
    78560#endif
    78661
    787     /*
    788      * Set up the diff state.
    789      */
    790     SCMDIFFSTATE State;
    791     State.cDiffs                = 0;
    792     State.pszFilename           = pszFilename;
    793     State.pLeft                 = pLeft;
    794     State.pRight                = pRight;
    795     State.fIgnoreEol            = fIgnoreEol;
    796     State.fIgnoreLeadingWhite   = fIgnoreLeadingWhite;
    797     State.fIgnoreTrailingWhite  = fIgnoreTrailingWhite;
    798     State.fSpecialChars         = fSpecialChars;
    799     State.cchTab                = cchTab;
    800     State.pDiff                 = pDiff;
    801 
    802     /*
    803      * Compare them line by line.
    804      */
    805     ScmStreamRewindForReading(pLeft);
    806     ScmStreamRewindForReading(pRight);
    807     const char *pchLeft;
    808     const char *pchRight;
    809 
    810     for (;;)
    811     {
    812         SCMEOL  enmEolLeft;
    813         size_t  cchLeft;
    814         pchLeft  = ScmStreamGetLine(pLeft,  &cchLeft,  &enmEolLeft);
    815 
    816         SCMEOL  enmEolRight;
    817         size_t  cchRight;
    818         pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
    819         if (!pchLeft || !pchRight)
    820             break;
    821 
    822         if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    823             scmDiffSynchronize(&State, 3);
    824     }
    825 
    826     /*
    827      * Deal with any remaining differences.
    828      */
    829     if (pchLeft)
    830         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
    831     else if (pchRight)
    832         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
    833 
    834     /*
    835      * Report any errors.
    836      */
    837     if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
    838         RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
    839     if (RT_FAILURE(ScmStreamGetStatus(pRight)))
    840         RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
    841 
    842     return State.cDiffs;
    843 }
    844 
    845 
    846 
    847 /* -=-=-=-=-=- settings -=-=-=-=-=- */
    848 
    849 /**
    850  * Init a settings structure with settings from @a pSrc.
    851  *
    852  * @returns IPRT status code
    853  * @param   pSettings           The settings.
    854  * @param   pSrc                The source settings.
    855  */
    856 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
    857 {
    858     *pSettings = *pSrc;
    859 
    860     int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
    861     if (RT_SUCCESS(rc))
    862     {
    863         rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
    864         if (RT_SUCCESS(rc))
    865         {
    866             rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
    867             if (RT_SUCCESS(rc))
    868                 return VINF_SUCCESS;
    869 
    870             RTStrFree(pSettings->pszFilterOutFiles);
    871         }
    872         RTStrFree(pSettings->pszFilterFiles);
    873     }
    874 
    875     pSettings->pszFilterFiles = NULL;
    876     pSettings->pszFilterOutFiles = NULL;
    877     pSettings->pszFilterOutDirs = NULL;
    878     return rc;
    879 }
    880 
    881 /**
    882  * Init a settings structure.
    883  *
    884  * @returns IPRT status code
    885  * @param   pSettings           The settings.
    886  */
    887 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
    888 {
    889     return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
    890 }
    891 
    892 /**
    893  * Deletes the settings, i.e. free any dynamically allocated content.
    894  *
    895  * @param   pSettings           The settings.
    896  */
    897 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
    898 {
    899     if (pSettings)
    900     {
    901         Assert(pSettings->cchTab != ~(unsigned)0);
    902         pSettings->cchTab = ~(unsigned)0;
    903 
    904         RTStrFree(pSettings->pszFilterFiles);
    905         pSettings->pszFilterFiles = NULL;
    906 
    907         RTStrFree(pSettings->pszFilterOutFiles);
    908         pSettings->pszFilterOutFiles = NULL;
    909 
    910         RTStrFree(pSettings->pszFilterOutDirs);
    911         pSettings->pszFilterOutDirs = NULL;
    912     }
    913 }
    914 
    915 
    916 /**
    917  * Processes a RTGetOpt result.
    918  *
    919  * @retval  VINF_SUCCESS if handled.
    920  * @retval  VERR_OUT_OF_RANGE if the option value was out of range.
    921  * @retval  VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
    922  *
    923  * @param   pSettings           The settings to change.
    924  * @param   rc                  The RTGetOpt return value.
    925  * @param   pValueUnion         The RTGetOpt value union.
    926  */
    927 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
    928 {
    929     switch (rc)
    930     {
    931         case SCMOPT_CONVERT_EOL:
    932             pSettings->fConvertEol = true;
    933             return VINF_SUCCESS;
    934         case SCMOPT_NO_CONVERT_EOL:
    935             pSettings->fConvertEol = false;
    936             return VINF_SUCCESS;
    937 
    938         case SCMOPT_CONVERT_TABS:
    939             pSettings->fConvertTabs = true;
    940             return VINF_SUCCESS;
    941         case SCMOPT_NO_CONVERT_TABS:
    942             pSettings->fConvertTabs = false;
    943             return VINF_SUCCESS;
    944 
    945         case SCMOPT_FORCE_FINAL_EOL:
    946             pSettings->fForceFinalEol = true;
    947             return VINF_SUCCESS;
    948         case SCMOPT_NO_FORCE_FINAL_EOL:
    949             pSettings->fForceFinalEol = false;
    950             return VINF_SUCCESS;
    951 
    952         case SCMOPT_FORCE_TRAILING_LINE:
    953             pSettings->fForceTrailingLine = true;
    954             return VINF_SUCCESS;
    955         case SCMOPT_NO_FORCE_TRAILING_LINE:
    956             pSettings->fForceTrailingLine = false;
    957             return VINF_SUCCESS;
    958 
    959         case SCMOPT_STRIP_TRAILING_BLANKS:
    960             pSettings->fStripTrailingBlanks = true;
    961             return VINF_SUCCESS;
    962         case SCMOPT_NO_STRIP_TRAILING_BLANKS:
    963             pSettings->fStripTrailingBlanks = false;
    964             return VINF_SUCCESS;
    965 
    966         case SCMOPT_STRIP_TRAILING_LINES:
    967             pSettings->fStripTrailingLines = true;
    968             return VINF_SUCCESS;
    969         case SCMOPT_NO_STRIP_TRAILING_LINES:
    970             pSettings->fStripTrailingLines = false;
    971             return VINF_SUCCESS;
    972 
    973         case SCMOPT_ONLY_SVN_DIRS:
    974             pSettings->fOnlySvnDirs = true;
    975             return VINF_SUCCESS;
    976         case SCMOPT_NOT_ONLY_SVN_DIRS:
    977             pSettings->fOnlySvnDirs = false;
    978             return VINF_SUCCESS;
    979 
    980         case SCMOPT_ONLY_SVN_FILES:
    981             pSettings->fOnlySvnFiles = true;
    982             return VINF_SUCCESS;
    983         case SCMOPT_NOT_ONLY_SVN_FILES:
    984             pSettings->fOnlySvnFiles = false;
    985             return VINF_SUCCESS;
    986 
    987         case SCMOPT_SET_SVN_EOL:
    988             pSettings->fSetSvnEol = true;
    989             return VINF_SUCCESS;
    990         case SCMOPT_DONT_SET_SVN_EOL:
    991             pSettings->fSetSvnEol = false;
    992             return VINF_SUCCESS;
    993 
    994         case SCMOPT_SET_SVN_EXECUTABLE:
    995             pSettings->fSetSvnExecutable = true;
    996             return VINF_SUCCESS;
    997         case SCMOPT_DONT_SET_SVN_EXECUTABLE:
    998             pSettings->fSetSvnExecutable = false;
    999             return VINF_SUCCESS;
    1000 
    1001         case SCMOPT_SET_SVN_KEYWORDS:
    1002             pSettings->fSetSvnKeywords = true;
    1003             return VINF_SUCCESS;
    1004         case SCMOPT_DONT_SET_SVN_KEYWORDS:
    1005             pSettings->fSetSvnKeywords = false;
    1006             return VINF_SUCCESS;
    1007 
    1008         case SCMOPT_TAB_SIZE:
    1009             if (   pValueUnion->u8 < 1
    1010                 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
    1011             {
    1012                 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
    1013                            pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
    1014                 return VERR_OUT_OF_RANGE;
    1015             }
    1016             pSettings->cchTab = pValueUnion->u8;
    1017             return VINF_SUCCESS;
    1018 
    1019         case SCMOPT_FILTER_OUT_DIRS:
    1020         case SCMOPT_FILTER_FILES:
    1021         case SCMOPT_FILTER_OUT_FILES:
    1022         {
    1023             char **ppsz = NULL;
    1024             switch (rc)
    1025             {
    1026                 case SCMOPT_FILTER_OUT_DIRS:    ppsz = &pSettings->pszFilterOutDirs; break;
    1027                 case SCMOPT_FILTER_FILES:       ppsz = &pSettings->pszFilterFiles; break;
    1028                 case SCMOPT_FILTER_OUT_FILES:   ppsz = &pSettings->pszFilterOutFiles; break;
    1029             }
    1030 
    1031             /*
    1032              * An empty string zaps the current list.
    1033              */
    1034             if (!*pValueUnion->psz)
    1035                 return RTStrATruncate(ppsz, 0);
    1036 
    1037             /*
    1038              * Non-empty strings are appended to the pattern list.
    1039              *
    1040              * Strip leading and trailing pattern separators before attempting
    1041              * to append it.  If it's just separators, don't do anything.
    1042              */
    1043             const char *pszSrc = pValueUnion->psz;
    1044             while (*pszSrc == '|')
    1045                 pszSrc++;
    1046             size_t cchSrc = strlen(pszSrc);
    1047             while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
    1048                 cchSrc--;
    1049             if (!cchSrc)
    1050                 return VINF_SUCCESS;
    1051 
    1052             return RTStrAAppendExN(ppsz, 2,
    1053                                    "|", *ppsz && **ppsz ? 1 : 0,
    1054                                    pszSrc, cchSrc);
    1055         }
    1056 
    1057         default:
    1058             return VERR_GETOPT_UNKNOWN_OPTION;
    1059     }
    1060 }
    1061 
    1062 /**
    1063  * Parses an option string.
    1064  *
    1065  * @returns IPRT status code.
    1066  * @param   pBase               The base settings structure to apply the options
    1067  *                              to.
    1068  * @param   pszOptions          The options to parse.
    1069  */
    1070 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
    1071 {
    1072     int    cArgs;
    1073     char **papszArgs;
    1074     int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
    1075     if (RT_SUCCESS(rc))
    1076     {
    1077         RTGETOPTUNION   ValueUnion;
    1078         RTGETOPTSTATE   GetOptState;
    1079         rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
    1080         if (RT_SUCCESS(rc))
    1081         {
    1082             while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    1083             {
    1084                 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
    1085                 if (RT_FAILURE(rc))
    1086                     break;
    1087             }
    1088         }
    1089         RTGetOptArgvFree(papszArgs);
    1090     }
    1091 
    1092     return rc;
    1093 }
    1094 
    1095 /**
    1096  * Parses an unterminated option string.
    1097  *
    1098  * @returns IPRT status code.
    1099  * @param   pBase               The base settings structure to apply the options
    1100  *                              to.
    1101  * @param   pchLine             The line.
    1102  * @param   cchLine             The line length.
    1103  */
    1104 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
    1105 {
    1106     char *pszLine = RTStrDupN(pchLine, cchLine);
    1107     if (!pszLine)
    1108         return VERR_NO_MEMORY;
    1109     int rc = scmSettingsBaseParseString(pBase, pszLine);
    1110     RTStrFree(pszLine);
    1111     return rc;
    1112 }
    1113 
    1114 /**
    1115  * Verifies the options string.
    1116  *
    1117  * @returns IPRT status code.
    1118  * @param   pszOptions          The options to verify .
    1119  */
    1120 static int scmSettingsBaseVerifyString(const char *pszOptions)
    1121 {
    1122     SCMSETTINGSBASE Base;
    1123     int rc = scmSettingsBaseInit(&Base);
    1124     if (RT_SUCCESS(rc))
    1125     {
    1126         rc = scmSettingsBaseParseString(&Base, pszOptions);
    1127         scmSettingsBaseDelete(&Base);
    1128     }
    1129     return rc;
    1130 }
    1131 
    1132 /**
    1133  * Loads settings found in editor and SCM settings directives within the
    1134  * document (@a pStream).
    1135  *
    1136  * @returns IPRT status code.
    1137  * @param   pBase               The settings base to load settings into.
    1138  * @param   pStream             The stream to scan for settings directives.
    1139  */
    1140 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
    1141 {
    1142     /** @todo Editor and SCM settings directives in documents.  */
    1143     return VINF_SUCCESS;
    1144 }
    1145 
    1146 /**
    1147  * Creates a new settings file struct, cloning @a pSettings.
    1148  *
    1149  * @returns IPRT status code.
    1150  * @param   ppSettings          Where to return the new struct.
    1151  * @param   pSettingsBase       The settings to inherit from.
    1152  */
    1153 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
    1154 {
    1155     PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
    1156     if (!pSettings)
    1157         return VERR_NO_MEMORY;
    1158     int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
    1159     if (RT_SUCCESS(rc))
    1160     {
    1161         pSettings->pDown   = NULL;
    1162         pSettings->pUp     = NULL;
    1163         pSettings->paPairs = NULL;
    1164         pSettings->cPairs  = 0;
    1165         *ppSettings = pSettings;
    1166         return VINF_SUCCESS;
    1167     }
    1168     RTMemFree(pSettings);
    1169     return rc;
    1170 }
    1171 
    1172 /**
    1173  * Destroys a settings structure.
    1174  *
    1175  * @param   pSettings           The settings structure to destroy.  NULL is OK.
    1176  */
    1177 static void scmSettingsDestroy(PSCMSETTINGS pSettings)
    1178 {
    1179     if (pSettings)
    1180     {
    1181         scmSettingsBaseDelete(&pSettings->Base);
    1182         for (size_t i = 0; i < pSettings->cPairs; i++)
    1183         {
    1184             RTStrFree(pSettings->paPairs[i].pszPattern);
    1185             RTStrFree(pSettings->paPairs[i].pszOptions);
    1186             pSettings->paPairs[i].pszPattern = NULL;
    1187             pSettings->paPairs[i].pszOptions = NULL;
    1188         }
    1189         RTMemFree(pSettings->paPairs);
    1190         pSettings->paPairs = NULL;
    1191         RTMemFree(pSettings);
    1192     }
    1193 }
    1194 
    1195 /**
    1196  * Adds a pattern/options pair to the settings structure.
    1197  *
    1198  * @returns IPRT status code.
    1199  * @param   pSettings           The settings.
    1200  * @param   pchLine             The line containing the unparsed pair.
    1201  * @param   cchLine             The length of the line.
    1202  */
    1203 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
    1204 {
    1205     /*
    1206      * Split the string.
    1207      */
    1208     const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
    1209     if (!pchOptions)
    1210         return VERR_INVALID_PARAMETER;
    1211     size_t cchPattern = pchOptions - pchLine;
    1212     size_t cchOptions = cchLine - cchPattern - 1;
    1213     pchOptions++;
    1214 
    1215     /* strip spaces everywhere */
    1216     while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
    1217         cchPattern--;
    1218     while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
    1219         cchPattern--, pchLine++;
    1220 
    1221     while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
    1222         cchOptions--;
    1223     while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
    1224         cchOptions--, pchOptions++;
    1225 
    1226     /* Quietly ignore empty patterns and empty options. */
    1227     if (!cchOptions || !cchPattern)
    1228         return VINF_SUCCESS;
    1229 
    1230     /*
    1231      * Add the pair and verify the option string.
    1232      */
    1233     uint32_t iPair = pSettings->cPairs;
    1234     if ((iPair % 32) == 0)
    1235     {
    1236         void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
    1237         if (!pvNew)
    1238             return VERR_NO_MEMORY;
    1239         pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
    1240     }
    1241 
    1242     pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
    1243     pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
    1244     int rc;
    1245     if (   pSettings->paPairs[iPair].pszPattern
    1246         && pSettings->paPairs[iPair].pszOptions)
    1247         rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
    1248     else
    1249         rc = VERR_NO_MEMORY;
    1250     if (RT_SUCCESS(rc))
    1251         pSettings->cPairs = iPair + 1;
    1252     else
    1253     {
    1254         RTStrFree(pSettings->paPairs[iPair].pszPattern);
    1255         RTStrFree(pSettings->paPairs[iPair].pszOptions);
    1256     }
    1257     return rc;
    1258 }
    1259 
    1260 /**
    1261  * Loads in the settings from @a pszFilename.
    1262  *
    1263  * @returns IPRT status code.
    1264  * @param   pSettings           Where to load the settings file.
    1265  * @param   pszFilename         The file to load.
    1266  */
    1267 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
    1268 {
    1269     SCMSTREAM Stream;
    1270     int rc = ScmStreamInitForReading(&Stream, pszFilename);
    1271     if (RT_FAILURE(rc))
    1272     {
    1273         RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
    1274         return rc;
    1275     }
    1276 
    1277     SCMEOL      enmEol;
    1278     const char *pchLine;
    1279     size_t      cchLine;
    1280     while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    1281     {
    1282         /* Ignore leading spaces. */
    1283         while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
    1284             pchLine++, cchLine--;
    1285 
    1286         /* Ignore empty lines and comment lines. */
    1287         if (cchLine < 1 || *pchLine == '#')
    1288             continue;
    1289 
    1290         /* What kind of line is it? */
    1291         const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
    1292         if (pchColon)
    1293             rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
    1294         else
    1295             rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
    1296         if (RT_FAILURE(rc))
    1297         {
    1298             RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
    1299             break;
    1300         }
    1301     }
    1302 
    1303     if (RT_SUCCESS(rc))
    1304     {
    1305         rc = ScmStreamGetStatus(&Stream);
    1306         if (RT_FAILURE(rc))
    1307             RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
    1308     }
    1309 
    1310     ScmStreamDelete(&Stream);
    1311     return rc;
    1312 }
    1313 
    1314 /**
    1315  * Parse the specified settings file creating a new settings struct from it.
    1316  *
    1317  * @returns IPRT status code
    1318  * @param   ppSettings          Where to return the new settings.
    1319  * @param   pszFilename         The file to parse.
    1320  * @param   pSettingsBase       The base settings we inherit from.
    1321  */
    1322 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
    1323 {
    1324     PSCMSETTINGS pSettings;
    1325     int rc = scmSettingsCreate(&pSettings, pSettingsBase);
    1326     if (RT_SUCCESS(rc))
    1327     {
    1328         rc = scmSettingsLoadFile(pSettings, pszFilename);
    1329         if (RT_SUCCESS(rc))
    1330         {
    1331             *ppSettings = pSettings;
    1332             return VINF_SUCCESS;
    1333         }
    1334 
    1335         scmSettingsDestroy(pSettings);
    1336     }
    1337     *ppSettings = NULL;
    1338     return rc;
    1339 }
    1340 
    1341 
    1342 /**
    1343  * Create an initial settings structure when starting processing a new file or
    1344  * directory.
    1345  *
    1346  * This will look for .scm-settings files from the root and down to the
    1347  * specified directory, combining them into the returned settings structure.
    1348  *
    1349  * @returns IPRT status code.
    1350  * @param   ppSettings          Where to return the pointer to the top stack
    1351  *                              object.
    1352  * @param   pBaseSettings       The base settings we inherit from (globals
    1353  *                              typically).
    1354  * @param   pszPath             The absolute path to the new directory or file.
    1355  */
    1356 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
    1357 {
    1358     *ppSettings = NULL;                 /* try shut up gcc. */
    1359 
    1360     /*
    1361      * We'll be working with a stack copy of the path.
    1362      */
    1363     char    szFile[RTPATH_MAX];
    1364     size_t  cchDir = strlen(pszPath);
    1365     if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
    1366         return VERR_FILENAME_TOO_LONG;
    1367 
    1368     /*
    1369      * Create the bottom-most settings.
    1370      */
    1371     PSCMSETTINGS pSettings;
    1372     int rc = scmSettingsCreate(&pSettings, pBaseSettings);
    1373     if (RT_FAILURE(rc))
    1374         return rc;
    1375 
    1376     /*
    1377      * Enumerate the path components from the root and down. Load any setting
    1378      * files we find.
    1379      */
    1380     size_t cComponents = RTPathCountComponents(pszPath);
    1381     for (size_t i = 1; i <= cComponents; i++)
    1382     {
    1383         rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
    1384         if (RT_SUCCESS(rc))
    1385             rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
    1386         if (RT_FAILURE(rc))
    1387             break;
    1388         if (RTFileExists(szFile))
    1389         {
    1390             rc = scmSettingsLoadFile(pSettings, szFile);
    1391             if (RT_FAILURE(rc))
    1392                 break;
    1393         }
    1394     }
    1395 
    1396     if (RT_SUCCESS(rc))
    1397         *ppSettings = pSettings;
    1398     else
    1399         scmSettingsDestroy(pSettings);
    1400     return rc;
    1401 }
    1402 
    1403 /**
    1404  * Pushes a new settings set onto the stack.
    1405  *
    1406  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1407  *                              element.  This will be used as input and output.
    1408  * @param   pSettings           The settings to push onto the stack.
    1409  */
    1410 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
    1411 {
    1412     PSCMSETTINGS pOld = *ppSettingsStack;
    1413     pSettings->pDown  = pOld;
    1414     pSettings->pUp    = NULL;
    1415     if (pOld)
    1416         pOld->pUp = pSettings;
    1417     *ppSettingsStack = pSettings;
    1418 }
    1419 
    1420 /**
    1421  * Pushes the settings of the specified directory onto the stack.
    1422  *
    1423  * We will load any .scm-settings in the directory.  A stack entry is added even
    1424  * if no settings file was found.
    1425  *
    1426  * @returns IPRT status code.
    1427  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1428  *                              element.  This will be used as input and output.
    1429  * @param   pszDir              The directory to do this for.
    1430  */
    1431 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
    1432 {
    1433     char szFile[RTPATH_MAX];
    1434     int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
    1435     if (RT_SUCCESS(rc))
    1436     {
    1437         PSCMSETTINGS pSettings;
    1438         rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
    1439         if (RT_SUCCESS(rc))
    1440         {
    1441             if (RTFileExists(szFile))
    1442                 rc = scmSettingsLoadFile(pSettings, szFile);
    1443             if (RT_SUCCESS(rc))
    1444             {
    1445                 scmSettingsStackPush(ppSettingsStack, pSettings);
    1446                 return VINF_SUCCESS;
    1447             }
    1448 
    1449             scmSettingsDestroy(pSettings);
    1450         }
    1451     }
    1452     return rc;
    1453 }
    1454 
    1455 
    1456 /**
    1457  * Pops a settings set off the stack.
    1458  *
    1459  * @returns The popped setttings.
    1460  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1461  *                              element.  This will be used as input and output.
    1462  */
    1463 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
    1464 {
    1465     PSCMSETTINGS pRet = *ppSettingsStack;
    1466     PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
    1467     *ppSettingsStack = pNew;
    1468     if (pNew)
    1469         pNew->pUp    = NULL;
    1470     if (pRet)
    1471     {
    1472         pRet->pUp    = NULL;
    1473         pRet->pDown  = NULL;
    1474     }
    1475     return pRet;
    1476 }
    1477 
    1478 /**
    1479  * Pops and destroys the top entry of the stack.
    1480  *
    1481  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    1482  *                              element.  This will be used as input and output.
    1483  */
    1484 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
    1485 {
    1486     scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
    1487 }
    1488 
    1489 /**
    1490  * Constructs the base settings for the specified file name.
    1491  *
    1492  * @returns IPRT status code.
    1493  * @param   pSettingsStack      The top element on the settings stack.
    1494  * @param   pszFilename         The file name.
    1495  * @param   pszBasename         The base name (pointer within @a pszFilename).
    1496  * @param   cchBasename         The length of the base name.  (For passing to
    1497  *                              RTStrSimplePatternMultiMatch.)
    1498  * @param   pBase               Base settings to initialize.
    1499  */
    1500 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
    1501                                         const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
    1502 {
    1503     int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
    1504     if (RT_SUCCESS(rc))
    1505     {
    1506         /* find the bottom entry in the stack. */
    1507         PCSCMSETTINGS pCur = pSettingsStack;
    1508         while (pCur->pDown)
    1509             pCur = pCur->pDown;
    1510 
    1511         /* Work our way up thru the stack and look for matching pairs. */
    1512         while (pCur)
    1513         {
    1514             size_t const cPairs = pCur->cPairs;
    1515             if (cPairs)
    1516             {
    1517                 for (size_t i = 0; i < cPairs; i++)
    1518                     if (   RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1519                                                         pszBasename,  cchBasename, NULL)
    1520                         || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    1521                                                         pszFilename,  RTSTR_MAX, NULL))
    1522                     {
    1523                         rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
    1524                         if (RT_FAILURE(rc))
    1525                             break;
    1526                     }
    1527                 if (RT_FAILURE(rc))
    1528                     break;
    1529             }
    1530 
    1531             /* advance */
    1532             pCur = pCur->pUp;
    1533         }
    1534     }
    1535     if (RT_FAILURE(rc))
    1536         scmSettingsBaseDelete(pBase);
    1537     return rc;
    1538 }
    1539 
    1540 
    1541 /* -=-=-=-=-=- misc -=-=-=-=-=- */
    1542 
    1543 
    1544 /**
    1545  * Prints a verbose message if the level is high enough.
    1546  *
    1547  * @param   pState              The rewrite state.  Optional.
    1548  * @param   iLevel              The required verbosity level.
    1549  * @param   pszFormat           The message format string.  Can be NULL if we
    1550  *                              only want to trigger the per file message.
    1551  * @param   ...                 Format arguments.
    1552  */
    1553 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
    1554 {
    1555     if (iLevel <= g_iVerbosity)
    1556     {
    1557         if (pState && !pState->fFirst)
    1558         {
    1559             RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
    1560             pState->fFirst = true;
    1561         }
    1562         if (pszFormat)
    1563         {
    1564             RTPrintf(pState
    1565                      ? "%s: info:   "
    1566                      : "%s: info: ",
    1567                      g_szProgName);
    1568             va_list va;
    1569             va_start(va, pszFormat);
    1570             RTPrintfV(pszFormat, va);
    1571             va_end(va);
    1572         }
    1573     }
    1574 }
    1575 
    1576 
    1577 /* -=-=-=-=-=- subversion -=-=-=-=-=- */
    1578 
    1579 #define SCM_WITHOUT_LIBSVN
    1580 
    1581 #ifdef SCM_WITHOUT_LIBSVN
    1582 
    1583 /**
    1584  * Callback that is call for each path to search.
    1585  */
    1586 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    1587 {
    1588     char   *pszDst = (char *)pvUser1;
    1589     size_t  cchDst = (size_t)pvUser2;
    1590     if (cchDst > cchPath)
    1591     {
    1592         memcpy(pszDst, pchPath, cchPath);
    1593         pszDst[cchPath] = '\0';
    1594 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1595         int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
    1596 #else
    1597         int rc = RTPathAppend(pszDst, cchDst, "svn");
    1598 #endif
    1599         if (   RT_SUCCESS(rc)
    1600             && RTFileExists(pszDst))
    1601             return VINF_SUCCESS;
    1602     }
    1603     return VERR_TRY_AGAIN;
    1604 }
    1605 
    1606 
    1607 /**
    1608  * Finds the svn binary.
    1609  *
    1610  * @param   pszPath             Where to store it.  Worst case, we'll return
    1611  *                              "svn" here.
    1612  * @param   cchPath             The size of the buffer pointed to by @a pszPath.
    1613  */
    1614 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
    1615 {
    1616     /** @todo code page fun... */
    1617     Assert(cchPath >= sizeof("svn"));
    1618 #ifdef RT_OS_WINDOWS
    1619     const char *pszEnvVar = RTEnvGet("Path");
    1620 #else
    1621     const char *pszEnvVar = RTEnvGet("PATH");
    1622 #endif
    1623     if (pszPath)
    1624     {
    1625 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1626         int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1627 #else
    1628         int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    1629 #endif
    1630         if (RT_SUCCESS(rc))
    1631             return;
    1632     }
    1633     strcpy(pszPath, "svn");
    1634 }
    1635 
    1636 
    1637 /**
    1638  * Construct a dot svn filename for the file being rewritten.
    1639  *
    1640  * @returns IPRT status code.
    1641  * @param   pState              The rewrite state (for the name).
    1642  * @param   pszDir              The directory, including ".svn/".
    1643  * @param   pszSuff             The filename suffix.
    1644  * @param   pszDst              The output buffer.  RTPATH_MAX in size.
    1645  */
    1646 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
    1647 {
    1648     strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
    1649     RTPathStripFilename(pszDst);
    1650 
    1651     int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
    1652     if (RT_SUCCESS(rc))
    1653     {
    1654         rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
    1655         if (RT_SUCCESS(rc))
    1656         {
    1657             size_t cchDst  = strlen(pszDst);
    1658             size_t cchSuff = strlen(pszSuff);
    1659             if (cchDst + cchSuff < RTPATH_MAX)
    1660             {
    1661                 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
    1662                 return VINF_SUCCESS;
    1663             }
    1664             else
    1665                 rc = VERR_BUFFER_OVERFLOW;
    1666         }
    1667     }
    1668     return rc;
    1669 }
    1670 
    1671 /**
    1672  * Interprets the specified string as decimal numbers.
    1673  *
    1674  * @returns true if parsed successfully, false if not.
    1675  * @param   pch                 The string (not terminated).
    1676  * @param   cch                 The string length.
    1677  * @param   pu                  Where to return the value.
    1678  */
    1679 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
    1680 {
    1681     size_t u = 0;
    1682     while (cch-- > 0)
    1683     {
    1684         char ch = *pch++;
    1685         if (ch < '0' || ch > '9')
    1686             return false;
    1687         u *= 10;
    1688         u += ch - '0';
    1689     }
    1690     *pu = u;
    1691     return true;
    1692 }
    1693 
    1694 #endif /* SCM_WITHOUT_LIBSVN */
    1695 
    1696 /**
    1697  * Checks if the file we're operating on is part of a SVN working copy.
    1698  *
    1699  * @returns true if it is, false if it isn't or we cannot tell.
    1700  * @param   pState              The rewrite state to work on.
    1701  */
    1702 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
    1703 {
    1704 #ifdef SCM_WITHOUT_LIBSVN
    1705     /*
    1706      * Hack: check if the .svn/text-base/<file>.svn-base file exists.
    1707      */
    1708     char szPath[RTPATH_MAX];
    1709     int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
    1710     if (RT_SUCCESS(rc))
    1711         return RTFileExists(szPath);
    1712 
    1713 #else
    1714     NOREF(pState);
    1715 #endif
    1716     return false;
    1717 }
    1718 
    1719 /**
    1720  * Queries the value of an SVN property.
    1721  *
    1722  * This will automatically adjust for scheduled changes.
    1723  *
    1724  * @returns IPRT status code.
    1725  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1726  * @retval  VERR_NOT_FOUND if the property wasn't found.
    1727  * @param   pState              The rewrite state to work on.
    1728  * @param   pszName             The property name.
    1729  * @param   ppszValue           Where to return the property value.  Free this
    1730  *                              using RTStrFree.  Optional.
    1731  */
    1732 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
    1733 {
    1734     /*
    1735      * Look it up in the scheduled changes.
    1736      */
    1737     uint32_t i = pState->cSvnPropChanges;
    1738     while (i-- > 0)
    1739         if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
    1740         {
    1741             const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1742             if (!pszValue)
    1743                 return VERR_NOT_FOUND;
    1744             if (ppszValue)
    1745                 return RTStrDupEx(ppszValue, pszValue);
    1746             return VINF_SUCCESS;
    1747         }
    1748 
    1749 #ifdef SCM_WITHOUT_LIBSVN
    1750     /*
    1751      * Hack: Read the .svn/props/<file>.svn-work file exists.
    1752      */
    1753     char szPath[RTPATH_MAX];
    1754     int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
    1755     if (RT_SUCCESS(rc) && !RTFileExists(szPath))
    1756         rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
    1757     if (RT_SUCCESS(rc))
    1758     {
    1759         SCMSTREAM Stream;
    1760         rc = ScmStreamInitForReading(&Stream, szPath);
    1761         if (RT_SUCCESS(rc))
    1762         {
    1763             /*
    1764              * The current format is K len\n<name>\nV len\n<value>\n" ... END.
    1765              */
    1766             rc = VERR_NOT_FOUND;
    1767             size_t const    cchName = strlen(pszName);
    1768             SCMEOL          enmEol;
    1769             size_t          cchLine;
    1770             const char     *pchLine;
    1771             while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    1772             {
    1773                 /*
    1774                  * Parse the 'K num' / 'END' line.
    1775                  */
    1776                 if (   cchLine == 3
    1777                     && !memcmp(pchLine, "END", 3))
    1778                     break;
    1779                 size_t cchKey;
    1780                 if (   cchLine < 3
    1781                     || pchLine[0] != 'K'
    1782                     || pchLine[1] != ' '
    1783                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
    1784                     || cchKey == 0
    1785                     || cchKey > 4096)
    1786                 {
    1787                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1788                     rc = VERR_PARSE_ERROR;
    1789                     break;
    1790                 }
    1791 
    1792                 /*
    1793                  * Match the key and skip to the value line.  Don't bother with
    1794                  * names containing EOL markers.
    1795                  */
    1796                 size_t const offKey = ScmStreamTell(&Stream);
    1797                 bool fMatch = cchName == cchKey;
    1798                 if (fMatch)
    1799                 {
    1800                     pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1801                     if (!pchLine)
    1802                         break;
    1803                     fMatch = cchLine == cchName
    1804                           && !memcmp(pchLine, pszName, cchName);
    1805                 }
    1806 
    1807                 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
    1808                     break;
    1809                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1810                     break;
    1811 
    1812                 /*
    1813                  * Read and Parse the 'V num' line.
    1814                  */
    1815                 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    1816                 if (!pchLine)
    1817                     break;
    1818                 size_t cchValue;
    1819                 if (   cchLine < 3
    1820                     || pchLine[0] != 'V'
    1821                     || pchLine[1] != ' '
    1822                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
    1823                     || cchValue > _1M)
    1824                 {
    1825                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    1826                     rc = VERR_PARSE_ERROR;
    1827                     break;
    1828                 }
    1829 
    1830                 /*
    1831                  * If we have a match, allocate a return buffer and read the
    1832                  * value into it.  Otherwise skip this value and continue
    1833                  * searching.
    1834                  */
    1835                 if (fMatch)
    1836                 {
    1837                     if (!ppszValue)
    1838                         rc = VINF_SUCCESS;
    1839                     else
    1840                     {
    1841                         char *pszValue;
    1842                         rc = RTStrAllocEx(&pszValue, cchValue + 1);
    1843                         if (RT_SUCCESS(rc))
    1844                         {
    1845                             rc = ScmStreamRead(&Stream, pszValue, cchValue);
    1846                             if (RT_SUCCESS(rc))
    1847                                 *ppszValue = pszValue;
    1848                             else
    1849                                 RTStrFree(pszValue);
    1850                         }
    1851                     }
    1852                     break;
    1853                 }
    1854 
    1855                 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
    1856                     break;
    1857                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    1858                     break;
    1859             }
    1860 
    1861             if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
    1862             {
    1863                 rc = ScmStreamGetStatus(&Stream);
    1864                 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
    1865             }
    1866             ScmStreamDelete(&Stream);
    1867         }
    1868     }
    1869 
    1870     if (rc == VERR_FILE_NOT_FOUND)
    1871         rc = VERR_NOT_FOUND;
    1872     return rc;
    1873 
    1874 #else
    1875     NOREF(pState);
    1876 #endif
    1877     return VERR_NOT_FOUND;
    1878 }
    1879 
    1880 
    1881 /**
    1882  * Schedules the setting of a property.
    1883  *
    1884  * @returns IPRT status code.
    1885  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    1886  * @param   pState              The rewrite state to work on.
    1887  * @param   pszName             The name of the property to set.
    1888  * @param   pszValue            The value.  NULL means deleting it.
    1889  */
    1890 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
    1891 {
    1892     /*
    1893      * Update any existing entry first.
    1894      */
    1895     size_t i = pState->cSvnPropChanges;
    1896     while (i-- > 0)
    1897         if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
    1898         {
    1899             if (!pszValue)
    1900             {
    1901                 RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1902                 pState->paSvnPropChanges[i].pszValue = NULL;
    1903             }
    1904             else
    1905             {
    1906                 char *pszCopy;
    1907                 int rc = RTStrDupEx(&pszCopy, pszValue);
    1908                 if (RT_FAILURE(rc))
    1909                     return rc;
    1910                 pState->paSvnPropChanges[i].pszValue = pszCopy;
    1911             }
    1912             return VINF_SUCCESS;
    1913         }
    1914 
    1915     /*
    1916      * Insert a new entry.
    1917      */
    1918     i = pState->cSvnPropChanges;
    1919     if ((i % 32) == 0)
    1920     {
    1921         void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
    1922         if (!pvNew)
    1923             return VERR_NO_MEMORY;
    1924         pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
    1925     }
    1926 
    1927     pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
    1928     pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
    1929     if (   pState->paSvnPropChanges[i].pszName
    1930         && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
    1931         pState->cSvnPropChanges = i + 1;
    1932     else
    1933     {
    1934         RTStrFree(pState->paSvnPropChanges[i].pszName);
    1935         pState->paSvnPropChanges[i].pszName = NULL;
    1936         RTStrFree(pState->paSvnPropChanges[i].pszValue);
    1937         pState->paSvnPropChanges[i].pszValue = NULL;
    1938         return VERR_NO_MEMORY;
    1939     }
    1940     return VINF_SUCCESS;
    1941 }
    1942 
    1943 
    1944 /**
    1945  * Schedules a property deletion.
    1946  *
    1947  * @returns IPRT status code.
    1948  * @param   pState              The rewrite state to work on.
    1949  * @param   pszName             The name of the property to delete.
    1950  */
    1951 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
    1952 {
    1953     return scmSvnSetProperty(pState, pszName, NULL);
    1954 }
    1955 
    1956 
    1957 /**
    1958  * Applies any SVN property changes to the work copy of the file.
    1959  *
    1960  * @returns IPRT status code.
    1961  * @param   pState              The rewrite state which SVN property changes
    1962  *                              should be applied.
    1963  */
    1964 static int scmSvnDisplayChanges(PSCMRWSTATE pState)
    1965 {
    1966     size_t i = pState->cSvnPropChanges;
    1967     while (i-- > 0)
    1968     {
    1969         const char *pszName  = pState->paSvnPropChanges[i].pszName;
    1970         const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    1971         if (pszValue)
    1972             ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1973         else
    1974             ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    1975     }
    1976 
    1977     return VINF_SUCCESS;
    1978 }
    1979 
    1980 /**
    1981  * Applies any SVN property changes to the work copy of the file.
    1982  *
    1983  * @returns IPRT status code.
    1984  * @param   pState              The rewrite state which SVN property changes
    1985  *                              should be applied.
    1986  */
    1987 static int scmSvnApplyChanges(PSCMRWSTATE pState)
    1988 {
    1989 #ifdef SCM_WITHOUT_LIBSVN
    1990     /*
    1991      * This sucks. We gotta find svn(.exe).
    1992      */
    1993     static char s_szSvnPath[RTPATH_MAX];
    1994     if (s_szSvnPath[0] == '\0')
    1995         scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
    1996 
    1997     /*
    1998      * Iterate thru the changes and apply them by starting the svn client.
    1999      */
    2000     for (size_t i = 0; i <pState->cSvnPropChanges; i++)
    2001     {
    2002         const char *apszArgv[6];
    2003         apszArgv[0] = s_szSvnPath;
    2004         apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
    2005         apszArgv[2] = pState->paSvnPropChanges[i].pszName;
    2006         int iArg = 3;
    2007         if (pState->paSvnPropChanges[i].pszValue)
    2008             apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
    2009         apszArgv[iArg++] = pState->pszFilename;
    2010         apszArgv[iArg++] = NULL;
    2011         ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
    2012                    apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
    2013 
    2014         RTPROCESS pid;
    2015         int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
    2016         if (RT_SUCCESS(rc))
    2017         {
    2018             RTPROCSTATUS Status;
    2019             rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
    2020             if (    RT_SUCCESS(rc)
    2021                 &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
    2022                      || Status.iStatus != 0) )
    2023             {
    2024                 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
    2025                            pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
    2026                            Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
    2027                            : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
    2028                            : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
    2029                            : "abducted by alien",
    2030                            Status.iStatus);
    2031                 return VERR_GENERAL_FAILURE;
    2032             }
    2033         }
    2034         if (RT_FAILURE(rc))
    2035         {
    2036             RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
    2037                        pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
    2038             return rc;
    2039         }
    2040     }
    2041 
    2042     return VINF_SUCCESS;
    2043 #else
    2044     return VERR_NOT_IMPLEMENTED;
    2045 #endif
    2046 }
    2047 
    2048 
    2049 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */
    2050 
    2051 
    2052 /**
    2053  * Strip trailing blanks (space & tab).
    2054  *
    2055  * @returns True if modified, false if not.
    2056  * @param   pIn                 The input stream.
    2057  * @param   pOut                The output stream.
    2058  * @param   pSettings           The settings.
    2059  */
    2060 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2061 {
    2062     if (!pSettings->fStripTrailingBlanks)
    2063         return false;
    2064 
    2065     bool        fModified = false;
    2066     SCMEOL      enmEol;
    2067     size_t      cchLine;
    2068     const char *pchLine;
    2069     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2070     {
    2071         int rc;
    2072         if (    cchLine == 0
    2073             ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
    2074             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2075         else
    2076         {
    2077             cchLine--;
    2078             while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
    2079                 cchLine--;
    2080             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2081             fModified = true;
    2082         }
    2083         if (RT_FAILURE(rc))
    2084             return false;
    2085     }
    2086     if (fModified)
    2087         ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
    2088     return fModified;
    2089 }
    2090 
    2091 /**
    2092  * Expand tabs.
    2093  *
    2094  * @returns True if modified, false if not.
    2095  * @param   pIn                 The input stream.
    2096  * @param   pOut                The output stream.
    2097  * @param   pSettings           The settings.
    2098  */
    2099 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2100 {
    2101     if (!pSettings->fConvertTabs)
    2102         return false;
    2103 
    2104     size_t const    cchTab = pSettings->cchTab;
    2105     bool            fModified = false;
    2106     SCMEOL          enmEol;
    2107     size_t          cchLine;
    2108     const char     *pchLine;
    2109     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2110     {
    2111         int rc;
    2112         const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
    2113         if (!pchTab)
    2114             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2115         else
    2116         {
    2117             size_t      offTab   = 0;
    2118             const char *pchChunk = pchLine;
    2119             for (;;)
    2120             {
    2121                 size_t  cchChunk = pchTab - pchChunk;
    2122                 offTab += cchChunk;
    2123                 ScmStreamWrite(pOut, pchChunk, cchChunk);
    2124 
    2125                 size_t  cchToTab = cchTab - offTab % cchTab;
    2126                 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
    2127                 offTab += cchToTab;
    2128 
    2129                 pchChunk = pchTab + 1;
    2130                 size_t  cchLeft  = cchLine - (pchChunk - pchLine);
    2131                 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
    2132                 if (!pchTab)
    2133                 {
    2134                     rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
    2135                     break;
    2136                 }
    2137             }
    2138 
    2139             fModified = true;
    2140         }
    2141         if (RT_FAILURE(rc))
    2142             return false;
    2143     }
    2144     if (fModified)
    2145         ScmVerbose(pState, 2, " * Expanded tabs\n");
    2146     return fModified;
    2147 }
    2148 
    2149 /**
    2150  * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
    2151  *
    2152  * @returns true if modifications were made, false if not.
    2153  * @param   pIn                 The input stream.
    2154  * @param   pOut                The output stream.
    2155  * @param   pSettings           The settings.
    2156  * @param   enmDesiredEol       The desired end of line indicator type.
    2157  * @param   pszDesiredSvnEol    The desired svn:eol-style.
    2158  */
    2159 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
    2160                              SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
    2161 {
    2162     if (!pSettings->fConvertEol)
    2163         return false;
    2164 
    2165     bool        fModified = false;
    2166     SCMEOL      enmEol;
    2167     size_t      cchLine;
    2168     const char *pchLine;
    2169     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    2170     {
    2171         if (   enmEol != enmDesiredEol
    2172             && enmEol != SCMEOL_NONE)
    2173         {
    2174             fModified = true;
    2175             enmEol = enmDesiredEol;
    2176         }
    2177         int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    2178         if (RT_FAILURE(rc))
    2179             return false;
    2180     }
    2181     if (fModified)
    2182         ScmVerbose(pState, 2, " * Converted EOL markers\n");
    2183 
    2184     /* Check svn:eol-style if appropriate */
    2185     if (   pSettings->fSetSvnEol
    2186         && scmSvnIsInWorkingCopy(pState))
    2187     {
    2188         char *pszEol;
    2189         int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    2190         if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    2191             || rc == VERR_NOT_FOUND)
    2192         {
    2193             if (rc == VERR_NOT_FOUND)
    2194                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
    2195             else
    2196                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    2197             int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    2198             if (RT_FAILURE(rc2))
    2199                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
    2200         }
    2201         if (RT_SUCCESS(rc))
    2202             RTStrFree(pszEol);
    2203     }
    2204 
    2205     /** @todo also check the subversion svn:eol-style state! */
    2206     return fModified;
    2207 }
    2208 
    2209 /**
    2210  * Force native end of line indicator.
    2211  *
    2212  * @returns true if modifications were made, false if not.
    2213  * @param   pIn                 The input stream.
    2214  * @param   pOut                The output stream.
    2215  * @param   pSettings           The settings.
    2216  */
    2217 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2218 {
    2219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2220     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
    2221 #else
    2222     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
    2223 #endif
    2224 }
    2225 
    2226 /**
    2227  * Force the stream to use LF as the end of line indicator.
    2228  *
    2229  * @returns true if modifications were made, false if not.
    2230  * @param   pIn                 The input stream.
    2231  * @param   pOut                The output stream.
    2232  * @param   pSettings           The settings.
    2233  */
    2234 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2235 {
    2236     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
    2237 }
    2238 
    2239 /**
    2240  * Force the stream to use CRLF as the end of line indicator.
    2241  *
    2242  * @returns true if modifications were made, false if not.
    2243  * @param   pIn                 The input stream.
    2244  * @param   pOut                The output stream.
    2245  * @param   pSettings           The settings.
    2246  */
    2247 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2248 {
    2249     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
    2250 }
    2251 
    2252 /**
    2253  * Strip trailing blank lines and/or make sure there is exactly one blank line
    2254  * at the end of the file.
    2255  *
    2256  * @returns true if modifications were made, false if not.
    2257  * @param   pIn                 The input stream.
    2258  * @param   pOut                The output stream.
    2259  * @param   pSettings           The settings.
    2260  *
    2261  * @remarks ASSUMES trailing white space has been removed already.
    2262  */
    2263 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2264 {
    2265     if (   !pSettings->fStripTrailingLines
    2266         && !pSettings->fForceTrailingLine
    2267         && !pSettings->fForceFinalEol)
    2268         return false;
    2269 
    2270     size_t const cLines = ScmStreamCountLines(pIn);
    2271 
    2272     /* Empty files remains empty. */
    2273     if (cLines <= 1)
    2274         return false;
    2275 
    2276     /* Figure out if we need to adjust the number of lines or not. */
    2277     size_t cLinesNew = cLines;
    2278 
    2279     if (   pSettings->fStripTrailingLines
    2280         && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    2281     {
    2282         while (   cLinesNew > 1
    2283                && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
    2284             cLinesNew--;
    2285     }
    2286 
    2287     if (    pSettings->fForceTrailingLine
    2288         && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    2289         cLinesNew++;
    2290 
    2291     bool fFixMissingEol = pSettings->fForceFinalEol
    2292                        && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
    2293 
    2294     if (   !fFixMissingEol
    2295         && cLines == cLinesNew)
    2296         return false;
    2297 
    2298     /* Copy the number of lines we've arrived at. */
    2299     ScmStreamRewindForReading(pIn);
    2300 
    2301     size_t cCopied = RT_MIN(cLinesNew, cLines);
    2302     ScmStreamCopyLines(pOut, pIn, cCopied);
    2303 
    2304     if (cCopied != cLinesNew)
    2305     {
    2306         while (cCopied++ < cLinesNew)
    2307             ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
    2308     }
    2309     /* Fix missing EOL if required. */
    2310     else if (fFixMissingEol)
    2311     {
    2312         if (ScmStreamGetEol(pIn) == SCMEOL_LF)
    2313             ScmStreamWrite(pOut, "\n", 1);
    2314         else
    2315             ScmStreamWrite(pOut, "\r\n", 2);
    2316     }
    2317 
    2318     ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
    2319     return true;
    2320 }
    2321 
    2322 /**
    2323  * Make sure there is no svn:executable keyword on the current file.
    2324  *
    2325  * @returns false - the state carries these kinds of changes.
    2326  * @param   pState              The rewriter state.
    2327  * @param   pIn                 The input stream.
    2328  * @param   pOut                The output stream.
    2329  * @param   pSettings           The settings.
    2330  */
    2331 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2332 {
    2333     if (   !pSettings->fSetSvnExecutable
    2334         || !scmSvnIsInWorkingCopy(pState))
    2335         return false;
    2336 
    2337     int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
    2338     if (RT_SUCCESS(rc))
    2339     {
    2340         ScmVerbose(pState, 2, " * removing svn:executable\n");
    2341         rc = scmSvnDelProperty(pState, "svn:executable");
    2342         if (RT_FAILURE(rc))
    2343             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2344     }
    2345     return false;
    2346 }
    2347 
    2348 /**
    2349  * Make sure the Id and Revision keywords are expanded.
    2350  *
    2351  * @returns false - the state carries these kinds of changes.
    2352  * @param   pState              The rewriter state.
    2353  * @param   pIn                 The input stream.
    2354  * @param   pOut                The output stream.
    2355  * @param   pSettings           The settings.
    2356  */
    2357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2358 {
    2359     if (   !pSettings->fSetSvnKeywords
    2360         || !scmSvnIsInWorkingCopy(pState))
    2361         return false;
    2362 
    2363     char *pszKeywords;
    2364     int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    2365     if (    RT_SUCCESS(rc)
    2366         && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
    2367             || !strstr(pszKeywords, "Revision")) )
    2368     {
    2369         if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
    2370             rc = RTStrAAppend(&pszKeywords, " Id Revision");
    2371         else if (!strstr(pszKeywords, "Id"))
    2372             rc = RTStrAAppend(&pszKeywords, " Id");
    2373         else
    2374             rc = RTStrAAppend(&pszKeywords, " Revision");
    2375         if (RT_SUCCESS(rc))
    2376         {
    2377             ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    2378             rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    2379             if (RT_FAILURE(rc))
    2380                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2381         }
    2382         else
    2383             RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
    2384         RTStrFree(pszKeywords);
    2385     }
    2386     else if (rc == VERR_NOT_FOUND)
    2387     {
    2388         ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    2389         rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    2390         if (RT_FAILURE(rc))
    2391             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    2392     }
    2393     else if (RT_SUCCESS(rc))
    2394         RTStrFree(pszKeywords);
    2395 
    2396     return false;
    2397 }
    2398 
    2399 /**
    2400  * Makefile.kup are empty files, enforce this.
    2401  *
    2402  * @returns true if modifications were made, false if not.
    2403  * @param   pIn                 The input stream.
    2404  * @param   pOut                The output stream.
    2405  * @param   pSettings           The settings.
    2406  */
    2407 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2408 {
    2409     /* These files should be zero bytes. */
    2410     if (pIn->cb == 0)
    2411         return false;
    2412     ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
    2413     return true;
    2414 }
    2415 
    2416 /**
    2417  * Rewrite a kBuild makefile.
    2418  *
    2419  * @returns true if modifications were made, false if not.
    2420  * @param   pIn                 The input stream.
    2421  * @param   pOut                The output stream.
    2422  * @param   pSettings           The settings.
    2423  *
    2424  * @todo
    2425  *
    2426  * Ideas for Makefile.kmk and Config.kmk:
    2427  *      - sort if1of/ifn1of sets.
    2428  *      - line continuation slashes should only be preceded by one space.
    2429  */
    2430 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2431 {
    2432     return false;
    2433 }
    2434 
    2435 /**
    2436  * Rewrite a C/C++ source or header file.
    2437  *
    2438  * @returns true if modifications were made, false if not.
    2439  * @param   pIn                 The input stream.
    2440  * @param   pOut                The output stream.
    2441  * @param   pSettings           The settings.
    2442  *
    2443  * @todo
    2444  *
    2445  * Ideas for C/C++:
    2446  *      - space after if, while, for, switch
    2447  *      - spaces in for (i=0;i<x;i++)
    2448  *      - complex conditional, bird style.
    2449  *      - remove unnecessary parentheses.
    2450  *      - sort defined RT_OS_*||  and RT_ARCH
    2451  *      - sizeof without parenthesis.
    2452  *      - defined without parenthesis.
    2453  *      - trailing spaces.
    2454  *      - parameter indentation.
    2455  *      - space after comma.
    2456  *      - while (x--); -> multi line + comment.
    2457  *      - else statement;
    2458  *      - space between function and left parenthesis.
    2459  *      - TODO, XXX, @todo cleanup.
    2460  *      - Space before/after '*'.
    2461  *      - ensure new line at end of file.
    2462  *      - Indentation of precompiler statements (#ifdef, #defines).
    2463  *      - space between functions.
    2464  *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    2465  */
    2466 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    2467 {
    2468 
    2469     return false;
    2470 }
    2471 
    2472 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
    2473 
    2474 /**
    2475  * Processes a file.
    2476  *
    2477  * @returns IPRT status code.
    2478  * @param   pState              The rewriter state.
    2479  * @param   pszFilename         The file name.
    2480  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2481  * @param   cchBasename         The length of the base name.  (For passing to
    2482  *                              RTStrSimplePatternMultiMatch.)
    2483  * @param   pBaseSettings       The base settings to use.  It's OK to modify
    2484  *                              these.
    2485  */
    2486 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2487                                PSCMSETTINGSBASE pBaseSettings)
    2488 {
    2489     /*
    2490      * Do the file level filtering.
    2491      */
    2492     if (   pBaseSettings->pszFilterFiles
    2493         && *pBaseSettings->pszFilterFiles
    2494         && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2495     {
    2496         ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
    2497         return VINF_SUCCESS;
    2498     }
    2499     if (   pBaseSettings->pszFilterOutFiles
    2500         && *pBaseSettings->pszFilterOutFiles
    2501         && (   RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
    2502             || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
    2503     {
    2504         ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
    2505         return VINF_SUCCESS;
    2506     }
    2507     if (   pBaseSettings->fOnlySvnFiles
    2508         && !scmSvnIsInWorkingCopy(pState))
    2509     {
    2510         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
    2511         return VINF_SUCCESS;
    2512     }
    2513 
    2514     /*
    2515      * Try find a matching rewrite config for this filename.
    2516      */
    2517     PCSCMCFGENTRY pCfg = NULL;
    2518     for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
    2519         if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
    2520         {
    2521             pCfg = &g_aConfigs[iCfg];
    2522             break;
    2523         }
    2524     if (!pCfg)
    2525     {
    2526         ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
    2527         return VINF_SUCCESS;
    2528     }
    2529     ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
    2530 
    2531     /*
    2532      * Create an input stream from the file and check that it's text.
    2533      */
    2534     SCMSTREAM Stream1;
    2535     int rc = ScmStreamInitForReading(&Stream1, pszFilename);
    2536     if (RT_FAILURE(rc))
    2537     {
    2538         RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
    2539         return rc;
    2540     }
    2541     if (ScmStreamIsText(&Stream1))
    2542     {
    2543         ScmVerbose(pState, 3, NULL);
    2544 
    2545         /*
    2546          * Gather SCM and editor settings from the stream.
    2547          */
    2548         rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
    2549         if (RT_SUCCESS(rc))
    2550         {
    2551             ScmStreamRewindForReading(&Stream1);
    2552 
    2553             /*
    2554              * Create two more streams for output and push the text thru all the
    2555              * rewriters, switching the two streams around when something is
    2556              * actually rewritten.  Stream1 remains unchanged.
    2557              */
    2558             SCMSTREAM Stream2;
    2559             rc = ScmStreamInitForWriting(&Stream2, &Stream1);
    2560             if (RT_SUCCESS(rc))
    2561             {
    2562                 SCMSTREAM Stream3;
    2563                 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
    2564                 if (RT_SUCCESS(rc))
    2565                 {
    2566                     bool        fModified = false;
    2567                     PSCMSTREAM  pIn       = &Stream1;
    2568                     PSCMSTREAM  pOut      = &Stream2;
    2569                     for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
    2570                     {
    2571                         bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
    2572                         if (fRc)
    2573                         {
    2574                             PSCMSTREAM pTmp = pOut;
    2575                             pOut = pIn == &Stream1 ? &Stream3 : pIn;
    2576                             pIn  = pTmp;
    2577                             fModified = true;
    2578                         }
    2579                         ScmStreamRewindForReading(pIn);
    2580                         ScmStreamRewindForWriting(pOut);
    2581                     }
    2582 
    2583                     rc = ScmStreamGetStatus(&Stream1);
    2584                     if (RT_SUCCESS(rc))
    2585                         rc = ScmStreamGetStatus(&Stream2);
    2586                     if (RT_SUCCESS(rc))
    2587                         rc = ScmStreamGetStatus(&Stream3);
    2588                     if (RT_SUCCESS(rc))
    2589                     {
    2590                         /*
    2591                          * If rewritten, write it back to disk.
    2592                          */
    2593                         if (fModified)
    2594                         {
    2595                             if (!g_fDryRun)
    2596                             {
    2597                                 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2598                                 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
    2599                                 if (RT_FAILURE(rc))
    2600                                     RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
    2601                             }
    2602                             else
    2603                             {
    2604                                 ScmVerbose(pState, 1, NULL);
    2605                                 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
    2606                                                g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
    2607                                 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    2608                             }
    2609                         }
    2610 
    2611                         /*
    2612                          * If pending SVN property changes, apply them.
    2613                          */
    2614                         if (pState->cSvnPropChanges && RT_SUCCESS(rc))
    2615                         {
    2616                             if (!g_fDryRun)
    2617                             {
    2618                                 rc = scmSvnApplyChanges(pState);
    2619                                 if (RT_FAILURE(rc))
    2620                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
    2621                             }
    2622                             else
    2623                                 scmSvnDisplayChanges(pState);
    2624                         }
    2625 
    2626                         if (!fModified && !pState->cSvnPropChanges)
    2627                             ScmVerbose(pState, 3, "no change\n", pszFilename);
    2628                     }
    2629                     else
    2630                         RTMsgError("%s: stream error %Rrc\n", pszFilename);
    2631                     ScmStreamDelete(&Stream3);
    2632                 }
    2633                 else
    2634                     RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2635                 ScmStreamDelete(&Stream2);
    2636             }
    2637             else
    2638                 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    2639         }
    2640         else
    2641             RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
    2642     }
    2643     else
    2644         ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
    2645     ScmStreamDelete(&Stream1);
    2646 
    2647     return rc;
    2648 }
    2649 
    2650 /**
    2651  * Processes a file.
    2652  *
    2653  * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
    2654  * directory recursion method.
    2655  *
    2656  * @returns IPRT status code.
    2657  * @param   pszFilename         The file name.
    2658  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2659  * @param   cchBasename         The length of the base name.  (For passing to
    2660  *                              RTStrSimplePatternMultiMatch.)
    2661  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2662  */
    2663 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
    2664                           PSCMSETTINGS pSettingsStack)
    2665 {
    2666     SCMSETTINGSBASE Base;
    2667     int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
    2668     if (RT_SUCCESS(rc))
    2669     {
    2670         SCMRWSTATE State;
    2671         State.fFirst           = false;
    2672         State.pszFilename      = pszFilename;
    2673         State.cSvnPropChanges  = 0;
    2674         State.paSvnPropChanges = NULL;
    2675 
    2676         rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
    2677 
    2678         size_t i = State.cSvnPropChanges;
    2679         while (i-- > 0)
    2680         {
    2681             RTStrFree(State.paSvnPropChanges[i].pszName);
    2682             RTStrFree(State.paSvnPropChanges[i].pszValue);
    2683         }
    2684         RTMemFree(State.paSvnPropChanges);
    2685 
    2686         scmSettingsBaseDelete(&Base);
    2687     }
    2688     return rc;
    2689 }
    2690 
    2691 
    2692 /**
    2693  * Tries to correct RTDIRENTRY_UNKNOWN.
    2694  *
    2695  * @returns Corrected type.
    2696  * @param   pszPath             The path to the object in question.
    2697  */
    2698 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
    2699 {
    2700     RTFSOBJINFO Info;
    2701     int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
    2702     if (RT_FAILURE(rc))
    2703         return RTDIRENTRYTYPE_UNKNOWN;
    2704     if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
    2705         return RTDIRENTRYTYPE_DIRECTORY;
    2706     if (RTFS_IS_FILE(Info.Attr.fMode))
    2707         return RTDIRENTRYTYPE_FILE;
    2708     return RTDIRENTRYTYPE_UNKNOWN;
    2709 }
    2710 
    2711 /**
    2712  * Recurse into a sub-directory and process all the files and directories.
    2713  *
    2714  * @returns IPRT status code.
    2715  * @param   pszBuf              Path buffer containing the directory path on
    2716  *                              entry.  This ends with a dot.  This is passed
    2717  *                              along when recursing in order to save stack space
    2718  *                              and avoid needless copying.
    2719  * @param   cchDir              Length of our path in pszbuf.
    2720  * @param   pEntry              Directory entry buffer.  This is also passed
    2721  *                              along when recursing to save stack space.
    2722  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2723  * @param   iRecursion          The recursion depth.  This is used to restrict
    2724  *                              the recursions.
    2725  */
    2726 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
    2727                                       PSCMSETTINGS pSettingsStack, unsigned iRecursion)
    2728 {
    2729     int rc;
    2730     Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
    2731 
    2732     /*
    2733      * Make sure we stop somewhere.
    2734      */
    2735     if (iRecursion > 128)
    2736     {
    2737         RTMsgError("recursion too deep: %d\n", iRecursion);
    2738         return VINF_SUCCESS; /* ignore */
    2739     }
    2740 
    2741     /*
    2742      * Check if it's excluded by --only-svn-dir.
    2743      */
    2744     if (pSettingsStack->Base.fOnlySvnDirs)
    2745     {
    2746         rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
    2747         if (RT_FAILURE(rc))
    2748         {
    2749             RTMsgError("RTPathAppend: %Rrc\n", rc);
    2750             return rc;
    2751         }
    2752         if (!RTDirExists(pszBuf))
    2753             return VINF_SUCCESS;
    2754 
    2755         Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
    2756         pszBuf[cchDir]     = '\0';
    2757         pszBuf[cchDir - 1] = '.';
    2758     }
    2759 
    2760     /*
    2761      * Try open and read the directory.
    2762      */
    2763     PRTDIR pDir;
    2764     rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
    2765     if (RT_FAILURE(rc))
    2766     {
    2767         RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
    2768         return rc;
    2769     }
    2770     for (;;)
    2771     {
    2772         /* Read the next entry. */
    2773         rc = RTDirRead(pDir, pEntry, NULL);
    2774         if (RT_FAILURE(rc))
    2775         {
    2776             if (rc == VERR_NO_MORE_FILES)
    2777                 rc = VINF_SUCCESS;
    2778             else
    2779                 RTMsgError("RTDirRead -> %Rrc\n", rc);
    2780             break;
    2781         }
    2782 
    2783         /* Skip '.' and '..'. */
    2784         if (    pEntry->szName[0] == '.'
    2785             &&  (   pEntry->cbName == 1
    2786                  || (   pEntry->cbName == 2
    2787                      && pEntry->szName[1] == '.')))
    2788             continue;
    2789 
    2790         /* Enter it into the buffer so we've got a full name to work
    2791            with when needed. */
    2792         if (pEntry->cbName + cchDir >= RTPATH_MAX)
    2793         {
    2794             RTMsgError("Skipping too long entry: %s", pEntry->szName);
    2795             continue;
    2796         }
    2797         memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
    2798 
    2799         /* Figure the type. */
    2800         RTDIRENTRYTYPE enmType = pEntry->enmType;
    2801         if (enmType == RTDIRENTRYTYPE_UNKNOWN)
    2802             enmType = scmFigureUnknownType(pszBuf);
    2803 
    2804         /* Process the file or directory, skip the rest. */
    2805         if (enmType == RTDIRENTRYTYPE_FILE)
    2806             rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
    2807         else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
    2808         {
    2809             /* Append the dot for the benefit of the pattern matching. */
    2810             if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
    2811             {
    2812                 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
    2813                 continue;
    2814             }
    2815             memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
    2816             size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
    2817 
    2818             if (   !pSettingsStack->Base.pszFilterOutDirs
    2819                 || !*pSettingsStack->Base.pszFilterOutDirs
    2820                 || (   !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2821                                                      pEntry->szName, pEntry->cbName, NULL)
    2822                     && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    2823                                                      pszBuf, cchSubDir, NULL)
    2824                    )
    2825                )
    2826             {
    2827                 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
    2828                 if (RT_SUCCESS(rc))
    2829                 {
    2830                     rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
    2831                     scmSettingsStackPopAndDestroy(&pSettingsStack);
    2832                 }
    2833             }
    2834         }
    2835         if (RT_FAILURE(rc))
    2836             break;
    2837     }
    2838     RTDirClose(pDir);
    2839     return rc;
    2840 
    2841 }
    2842 
    2843 /**
    2844  * Process a directory tree.
    2845  *
    2846  * @returns IPRT status code.
    2847  * @param   pszDir              The directory to start with.  This is pointer to
    2848  *                              a RTPATH_MAX sized buffer.
    2849  */
    2850 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
    2851 {
    2852     /*
    2853      * Setup the recursion.
    2854      */
    2855     int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
    2856     if (RT_SUCCESS(rc))
    2857     {
    2858         RTDIRENTRY Entry;
    2859         rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
    2860     }
    2861     else
    2862         RTMsgError("RTPathAppend: %Rrc\n", rc);
    2863     return rc;
    2864 }
    2865 
    2866 
    2867 /**
    2868  * Processes a file or directory specified as an command line argument.
    2869  *
    2870  * @returns IPRT status code
    2871  * @param   pszSomething        What we found in the command line arguments.
    2872  * @param   pSettingsStack      The settings stack (pointer to the top element).
    2873  */
    2874 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
    2875 {
    2876     char szBuf[RTPATH_MAX];
    2877     int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
    2878     if (RT_SUCCESS(rc))
    2879     {
    2880         RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
    2881 
    2882         PSCMSETTINGS pSettings;
    2883         rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
    2884         if (RT_SUCCESS(rc))
    2885         {
    2886             scmSettingsStackPush(&pSettingsStack, pSettings);
    2887 
    2888             if (RTFileExists(szBuf))
    2889             {
    2890                 const char *pszBasename = RTPathFilename(szBuf);
    2891                 if (pszBasename)
    2892                 {
    2893                     size_t cchBasename = strlen(pszBasename);
    2894                     rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
    2895                 }
    2896                 else
    2897                 {
    2898                     RTMsgError("RTPathFilename: NULL\n");
    2899                     rc = VERR_IS_A_DIRECTORY;
    2900                 }
    2901             }
    2902             else
    2903                 rc = scmProcessDirTree(szBuf, pSettingsStack);
    2904 
    2905             PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
    2906             Assert(pPopped == pSettings);
    2907             scmSettingsDestroy(pSettings);
    2908         }
    2909         else
    2910             RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
    2911     }
    2912     else
    2913         RTMsgError("RTPathAbs: %Rrc\n", rc);
    2914     return rc;
    2915 }
    2916 
    2917 int main(int argc, char **argv)
    2918 {
    2919     int rc = RTR3InitExe(argc, &argv, 0);
    2920     if (RT_FAILURE(rc))
    2921         return 1;
    2922 
    2923     /*
    2924      * Init the settings.
    2925      */
    2926     PSCMSETTINGS pSettings;
    2927     rc = scmSettingsCreate(&pSettings, &g_Defaults);
    2928     if (RT_FAILURE(rc))
    2929     {
    2930         RTMsgError("scmSettingsCreate: %Rrc\n", rc);
    2931         return 1;
    2932     }
    2933 
    2934     /*
    2935      * Parse arguments and process input in order (because this is the only
    2936      * thing that works at the moment).
    2937      */
    2938     static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
    2939     {
    2940         { "--dry-run",                          'd',                                    RTGETOPT_REQ_NOTHING },
    2941         { "--real-run",                         'D',                                    RTGETOPT_REQ_NOTHING },
    2942         { "--file-filter",                      'f',                                    RTGETOPT_REQ_STRING  },
    2943         { "--quiet",                            'q',                                    RTGETOPT_REQ_NOTHING },
    2944         { "--verbose",                          'v',                                    RTGETOPT_REQ_NOTHING },
    2945         { "--diff-ignore-eol",                  SCMOPT_DIFF_IGNORE_EOL,                 RTGETOPT_REQ_NOTHING },
    2946         { "--diff-no-ignore-eol",               SCMOPT_DIFF_NO_IGNORE_EOL,              RTGETOPT_REQ_NOTHING },
    2947         { "--diff-ignore-space",                SCMOPT_DIFF_IGNORE_SPACE,               RTGETOPT_REQ_NOTHING },
    2948         { "--diff-no-ignore-space",             SCMOPT_DIFF_NO_IGNORE_SPACE,            RTGETOPT_REQ_NOTHING },
    2949         { "--diff-ignore-leading-space",        SCMOPT_DIFF_IGNORE_LEADING_SPACE,       RTGETOPT_REQ_NOTHING },
    2950         { "--diff-no-ignore-leading-space",     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,    RTGETOPT_REQ_NOTHING },
    2951         { "--diff-ignore-trailing-space",       SCMOPT_DIFF_IGNORE_TRAILING_SPACE,      RTGETOPT_REQ_NOTHING },
    2952         { "--diff-no-ignore-trailing-space",    SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,   RTGETOPT_REQ_NOTHING },
    2953         { "--diff-special-chars",               SCMOPT_DIFF_SPECIAL_CHARS,              RTGETOPT_REQ_NOTHING },
    2954         { "--diff-no-special-chars",            SCMOPT_DIFF_NO_SPECIAL_CHARS,           RTGETOPT_REQ_NOTHING },
    2955     };
    2956     memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
    2957 
    2958     RTGETOPTUNION   ValueUnion;
    2959     RTGETOPTSTATE   GetOptState;
    2960     rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
    2961     AssertReleaseRCReturn(rc, 1);
    2962     size_t          cProcessed = 0;
    2963 
    2964     while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    2965     {
    2966         switch (rc)
    2967         {
    2968             case 'd':
    2969                 g_fDryRun = true;
    2970                 break;
    2971             case 'D':
    2972                 g_fDryRun = false;
    2973                 break;
    2974 
    2975             case 'f':
    2976                 g_pszFileFilter = ValueUnion.psz;
    2977                 break;
    2978 
    2979             case 'h':
    2980                 RTPrintf("VirtualBox Source Code Massager\n"
    2981                          "\n"
    2982                          "Usage: %s [options] <files & dirs>\n"
    2983                          "\n"
    2984                          "Options:\n", g_szProgName);
    2985                 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
    2986                 {
    2987                     bool fAdvanceTwo = false;
    2988                     if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
    2989                     {
    2990                         fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
    2991                                    && (   strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
    2992                                        || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
    2993                                        || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
    2994                                       );
    2995                         if (fAdvanceTwo)
    2996                             RTPrintf("  %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
    2997                         else
    2998                             RTPrintf("  %s\n", s_aOpts[i].pszLong);
    2999                     }
    3000                     else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
    3001                         RTPrintf("  %s string\n", s_aOpts[i].pszLong);
    3002                     else
    3003                         RTPrintf("  %s value\n", s_aOpts[i].pszLong);
    3004                     switch (s_aOpts[i].iShort)
    3005                     {
    3006                         case SCMOPT_CONVERT_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertEol); break;
    3007                         case SCMOPT_CONVERT_TABS:           RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertTabs); break;
    3008                         case SCMOPT_FORCE_FINAL_EOL:        RTPrintf("      Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
    3009                         case SCMOPT_FORCE_TRAILING_LINE:    RTPrintf("      Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
    3010                         case SCMOPT_STRIP_TRAILING_BLANKS:  RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
    3011                         case SCMOPT_STRIP_TRAILING_LINES:   RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
    3012                         case SCMOPT_ONLY_SVN_DIRS:          RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
    3013                         case SCMOPT_ONLY_SVN_FILES:         RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
    3014                         case SCMOPT_SET_SVN_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
    3015                         case SCMOPT_SET_SVN_EXECUTABLE:     RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
    3016                         case SCMOPT_SET_SVN_KEYWORDS:       RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
    3017                         case SCMOPT_TAB_SIZE:               RTPrintf("      Default: %u\n", g_Defaults.cchTab); break;
    3018                         case SCMOPT_FILTER_OUT_DIRS:        RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutDirs); break;
    3019                         case SCMOPT_FILTER_FILES:           RTPrintf("      Default: %s\n", g_Defaults.pszFilterFiles); break;
    3020                         case SCMOPT_FILTER_OUT_FILES:       RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutFiles); break;
    3021                     }
    3022                     i += fAdvanceTwo;
    3023                 }
    3024                 return 1;
    3025 
    3026             case 'q':
    3027                 g_iVerbosity = 0;
    3028                 break;
    3029 
    3030             case 'v':
    3031                 g_iVerbosity++;
    3032                 break;
    3033 
    3034             case 'V':
    3035             {
    3036                 /* The following is assuming that svn does it's job here. */
    3037                 static const char s_szRev[] = "$Revision$";
    3038                 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
    3039                 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
    3040                 return 0;
    3041             }
    3042 
    3043             case SCMOPT_DIFF_IGNORE_EOL:
    3044                 g_fDiffIgnoreEol = true;
    3045                 break;
    3046             case SCMOPT_DIFF_NO_IGNORE_EOL:
    3047                 g_fDiffIgnoreEol = false;
    3048                 break;
    3049 
    3050             case SCMOPT_DIFF_IGNORE_SPACE:
    3051                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
    3052                 break;
    3053             case SCMOPT_DIFF_NO_IGNORE_SPACE:
    3054                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
    3055                 break;
    3056 
    3057             case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
    3058                 g_fDiffIgnoreLeadingWS = true;
    3059                 break;
    3060             case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
    3061                 g_fDiffIgnoreLeadingWS = false;
    3062                 break;
    3063 
    3064             case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
    3065                 g_fDiffIgnoreTrailingWS = true;
    3066                 break;
    3067             case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
    3068                 g_fDiffIgnoreTrailingWS = false;
    3069                 break;
    3070 
    3071             case SCMOPT_DIFF_SPECIAL_CHARS:
    3072                 g_fDiffSpecialChars = true;
    3073                 break;
    3074             case SCMOPT_DIFF_NO_SPECIAL_CHARS:
    3075                 g_fDiffSpecialChars = false;
    3076                 break;
    3077 
    3078             case VINF_GETOPT_NOT_OPTION:
    3079             {
    3080                 if (!g_fDryRun)
    3081                 {
    3082                     if (!cProcessed)
    3083                     {
    3084                         RTPrintf("%s: Warning! This program will make changes to your source files and\n"
    3085                                  "%s:          there is a slight risk that bugs or a full disk may cause\n"
    3086                                  "%s:          LOSS OF DATA.   So, please make sure you have checked in\n"
    3087                                  "%s:          all your changes already.  If you didn't, then don't blame\n"
    3088                                  "%s:          anyone for not warning you!\n"
    3089                                  "%s:\n"
    3090                                  "%s:          Press any key to continue...\n",
    3091                                  g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
    3092                                  g_szProgName, g_szProgName);
    3093                         RTStrmGetCh(g_pStdIn);
    3094                     }
    3095                     cProcessed++;
    3096                 }
    3097                 rc = scmProcessSomething(ValueUnion.psz, pSettings);
    3098                 if (RT_FAILURE(rc))
    3099                     return rc;
    3100                 break;
    3101             }
    3102 
    3103             default:
    3104             {
    3105                 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
    3106                 if (RT_SUCCESS(rc2))
    3107                     break;
    3108                 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
    3109                     return 2;
    3110                 return RTGetOptPrintError(rc, &ValueUnion);
    3111             }
    3112         }
    3113     }
    3114 
    3115     scmSettingsDestroy(pSettings);
    3116     return 0;
    3117 }
    3118 
  • trunk/src/bldprogs/scmstream.cpp

    r40528 r40530  
    2121#include <iprt/assert.h>
    2222#include <iprt/ctype.h>
    23 //#include <iprt/dir.h>
    24 //#include <iprt/env.h>
    2523#include <iprt/file.h>
    2624#include <iprt/err.h>
    27 //#include <iprt/getopt.h>
    28 //#include <iprt/initterm.h>
    2925#include <iprt/mem.h>
    30 //#include <iprt/message.h>
    31 //#include <iprt/param.h>
    32 //#include <iprt/path.h>
    33 //#include <iprt/process.h>
    34 //#include <iprt/stream.h>
    3526#include <iprt/string.h>
    3627
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