- Timestamp:
- Mar 19, 2012 11:13:17 AM (13 years ago)
- Location:
- trunk/src/bldprogs
- Files:
-
- 3 edited
- 2 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bldprogs/Makefile.kmk
r40528 r40530 38 38 scm_SOURCES = \ 39 39 scm.cpp \ 40 scmstream.cpp 40 scmstream.cpp \ 41 scmdiff.cpp 41 42 scm_LIBS = \ 42 43 $(LIB_RUNTIME) -
trunk/src/bldprogs/scm.cpp
r40528 r40530 36 36 37 37 #include "scmstream.h" 38 #include "scmdiff.h" 38 39 39 40 … … 116 117 typedef SCMCFGENTRY const *PCSCMCFGENTRY; 117 118 118 119 /**120 * Diff state.121 */122 typedef struct SCMDIFFSTATE123 {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 119 146 120 /** … … 437 411 438 412 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 else465 {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 else503 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 the512 * resync.513 *514 *515 * @returns New pState->cDiff value (just to return something).516 * @param pState The diff state. The cDiffs member will be517 * 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 used521 * 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 else538 {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 else550 {551 iRight = c;552 cRight = 0;553 }554 }555 556 /*557 * Print header if it's the first difference558 */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 == 0566 ? 'a'567 : cRight == 0568 ? '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 else577 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 stuff601 * 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 != cchRight624 || (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 != cchRight640 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)641 || memcmp(pchLeft, pchRight, cchLeft))642 {643 if ( pState->fIgnoreTrailingWhite644 || 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 cMatches685 * 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 the690 * 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 preceding700 * 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 the706 * 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 the733 * 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 the764 * 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 human774 * 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_STRICT783 ScmStreamCheckItegrity(pLeft);784 ScmStreamCheckItegrity(pRight);785 #endif786 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 413 847 414 /* -=-=-=-=-=- settings -=-=-=-=-=- */ 415 848 416 849 417 /** -
trunk/src/bldprogs/scmdiff.cpp
r40528 r40530 21 21 #include <iprt/assert.h> 22 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 23 #include <iprt/message.h> 31 #include <iprt/param.h>32 #include <iprt/path.h>33 #include <iprt/process.h>34 24 #include <iprt/stream.h> 35 25 #include <iprt/string.h> 36 26 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" 297 28 298 29 … … 300 31 * Global Variables * 301 32 *******************************************************************************/ 302 static const char g_szProgName[] = "scm";303 static const char *g_pszChangedSuff = "";304 33 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 -=-=-=-=-=- */ 34 440 35 441 36 … … 843 438 } 844 439 845 846 847 /* -=-=-=-=-=- settings -=-=-=-=-=- */848 849 /**850 * Init a settings structure with settings from @a pSrc.851 *852 * @returns IPRT status code853 * @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 code885 * @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 < 11010 || 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 attempting1041 * 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 options1067 * 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 options1100 * 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 the1134 * 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].pszPattern1246 && pSettings->paPairs[iPair].pszOptions)1247 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);1248 else1249 rc = VERR_NO_MEMORY;1250 if (RT_SUCCESS(rc))1251 pSettings->cPairs = iPair + 1;1252 else1253 {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 else1295 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 code1318 * @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 or1344 * directory.1345 *1346 * This will look for .scm-settings files from the root and down to the1347 * 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 stack1351 * object.1352 * @param pBaseSettings The base settings we inherit from (globals1353 * 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 setting1378 * 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 else1399 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 stack1407 * 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 even1424 * if no settings file was found.1425 *1426 * @returns IPRT status code.1427 * @param ppSettingsStack The pointer to the pointer to the top stack1428 * 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 stack1461 * 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 stack1482 * 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 to1497 * 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 we1550 * 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(pState1565 ? "%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_LIBSVN1580 1581 #ifdef SCM_WITHOUT_LIBSVN1582 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 #else1597 int rc = RTPathAppend(pszDst, cchDst, "svn");1598 #endif1599 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 return1611 * "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_WINDOWS1619 const char *pszEnvVar = RTEnvGet("Path");1620 #else1621 const char *pszEnvVar = RTEnvGet("PATH");1622 #endif1623 if (pszPath)1624 {1625 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1626 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1627 #else1628 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1629 #endif1630 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 else1665 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_LIBSVN1705 /*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 #else1714 NOREF(pState);1715 #endif1716 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 this1730 * 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_LIBSVN1750 /*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 == 31777 && !memcmp(pchLine, "END", 3))1778 break;1779 size_t cchKey;1780 if ( cchLine < 31781 || pchLine[0] != 'K'1782 || pchLine[1] != ' '1783 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)1784 || cchKey == 01785 || 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 with1794 * 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 == cchName1804 && !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 < 31820 || 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 the1832 * value into it. Otherwise skip this value and continue1833 * searching.1834 */1835 if (fMatch)1836 {1837 if (!ppszValue)1838 rc = VINF_SUCCESS;1839 else1840 {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 else1849 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 #else1875 NOREF(pState);1876 #endif1877 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 else1905 {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].pszName1930 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )1931 pState->cSvnPropChanges = i + 1;1932 else1933 {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 changes1962 * 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 else1974 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 changes1985 * should be applied.1986 */1987 static int scmSvnApplyChanges(PSCMRWSTATE pState)1988 {1989 #ifdef SCM_WITHOUT_LIBSVN1990 /*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_NORMAL2022 || 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 #else2044 return VERR_NOT_IMPLEMENTED;2045 #endif2046 }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 == 02073 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )2074 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);2075 else2076 {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 else2116 {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 != enmDesiredEol2172 && 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->fSetSvnEol2186 && 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 else2196 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 #else2222 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");2223 #endif2224 }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 line2254 * 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->fStripTrailingLines2266 && !pSettings->fForceTrailingLine2267 && !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->fStripTrailingLines2280 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))2281 {2282 while ( cLinesNew > 12283 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))2284 cLinesNew--;2285 }2286 2287 if ( pSettings->fForceTrailingLine2288 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))2289 cLinesNew++;2290 2291 bool fFixMissingEol = pSettings->fForceFinalEol2292 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;2293 2294 if ( !fFixMissingEol2295 && 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 else2315 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->fSetSvnExecutable2334 || !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->fSetSvnKeywords2360 || !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 else2374 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 else2383 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 * @todo2425 *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 * @todo2444 *2445 * Ideas for C/C++:2446 * - space after if, while, for, switch2447 * - spaces in for (i=0;i<x;i++)2448 * - complex conditional, bird style.2449 * - remove unnecessary parentheses.2450 * - sort defined RT_OS_*|| and RT_ARCH2451 * - 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 to2482 * RTStrSimplePatternMultiMatch.)2483 * @param pBaseSettings The base settings to use. It's OK to modify2484 * 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->pszFilterFiles2493 && *pBaseSettings->pszFilterFiles2494 && !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->pszFilterOutFiles2500 && *pBaseSettings->pszFilterOutFiles2501 && ( 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->fOnlySvnFiles2508 && !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 the2555 * rewriters, switching the two streams around when something is2556 * 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 else2603 {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 else2623 scmSvnDisplayChanges(pState);2624 }2625 2626 if (!fModified && !pState->cSvnPropChanges)2627 ScmVerbose(pState, 3, "no change\n", pszFilename);2628 }2629 else2630 RTMsgError("%s: stream error %Rrc\n", pszFilename);2631 ScmStreamDelete(&Stream3);2632 }2633 else2634 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2635 ScmStreamDelete(&Stream2);2636 }2637 else2638 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2639 }2640 else2641 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);2642 }2643 else2644 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 the2654 * 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 to2660 * 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 on2716 * entry. This ends with a dot. This is passed2717 * along when recursing in order to save stack space2718 * and avoid needless copying.2719 * @param cchDir Length of our path in pszbuf.2720 * @param pEntry Directory entry buffer. This is also passed2721 * 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 restrict2724 * 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 else2779 RTMsgError("RTDirRead -> %Rrc\n", rc);2780 break;2781 }2782 2783 /* Skip '.' and '..'. */2784 if ( pEntry->szName[0] == '.'2785 && ( pEntry->cbName == 12786 || ( pEntry->cbName == 22787 && pEntry->szName[1] == '.')))2788 continue;2789 2790 /* Enter it into the buffer so we've got a full name to work2791 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.pszFilterOutDirs2819 || !*pSettingsStack->Base.pszFilterOutDirs2820 || ( !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 to2848 * 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 else2862 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 code2871 * @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 else2897 {2898 RTMsgError("RTPathFilename: NULL\n");2899 rc = VERR_IS_A_DIRECTORY;2900 }2901 }2902 else2903 rc = scmProcessDirTree(szBuf, pSettingsStack);2904 2905 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);2906 Assert(pPopped == pSettings);2907 scmSettingsDestroy(pSettings);2908 }2909 else2910 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);2911 }2912 else2913 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 only2936 * 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-") != NULL2992 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL2993 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL2994 );2995 if (fAdvanceTwo)2996 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);2997 else2998 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 else3003 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 1 1 /* $Id$ */ 2 2 /** @file 3 * IPRT Testcase / Tool - Source Code Massager .3 * IPRT Testcase / Tool - Source Code Massager Diff Code. 4 4 */ 5 5 … … 16 16 */ 17 17 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 34 21 #include <iprt/stream.h> 35 #include <iprt/string.h>36 37 22 #include "scmstream.h" 38 23 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 24 RT_C_DECLS_BEGIN 118 25 119 26 /** … … 144 51 typedef SCMDIFFSTATE *PSCMDIFFSTATE; 145 52 146 /**147 * Source Code Massager Settings.148 */149 typedef struct SCMSETTINGSBASE150 {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 the170 * base names. */171 char *pszFilterFiles;172 /** Filter out files matching the following patterns. This is applied to base173 * names as well as the absolute paths. */174 char *pszFilterOutFiles;175 /** Filter out directories matching the following patterns. This is applied176 * to base names as well as the absolute paths. All absolute paths ends with a177 * slash and dot ("/."). */178 char *pszFilterOutDirs;179 } SCMSETTINGSBASE;180 /** Pointer to massager settings. */181 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;182 53 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 even188 * 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 SCMOPT192 {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_END232 } SCMOPT;233 234 235 /**236 * File/dir pattern + options.237 */238 typedef struct SCMPATRNOPTPAIR239 {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 any253 * .scm-settings file found in a directory we recurse into. When recursing in254 * 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 the257 * unqualified base settings and then there are the settings which applies to a258 * 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 applies261 * to.262 *263 * We parse the base options into the Base member and put the others into the264 * paPairs array.265 */266 typedef struct SCMSETTINGS267 {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_kup370 };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_kmk380 };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_CPP391 };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_CPP401 };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_SvnKeywords411 };412 413 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =414 {415 rewrite_ForceLF,416 rewrite_ExpandTabs,417 rewrite_StripTrailingBlanks418 };419 420 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =421 {422 rewrite_ForceCRLF,423 rewrite_ExpandTabs,424 rewrite_StripTrailingBlanks425 };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 else465 {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 else503 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 the512 * resync.513 *514 *515 * @returns New pState->cDiff value (just to return something).516 * @param pState The diff state. The cDiffs member will be517 * 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 used521 * 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 else538 {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 else550 {551 iRight = c;552 cRight = 0;553 }554 }555 556 /*557 * Print header if it's the first difference558 */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 == 0566 ? 'a'567 : cRight == 0568 ? '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 else577 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 stuff601 * 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 != cchRight624 || (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 != cchRight640 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)641 || memcmp(pchLeft, pchRight, cchLeft))642 {643 if ( pState->fIgnoreTrailingWhite644 || 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 cMatches685 * 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 the690 * 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 preceding700 * 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 the706 * 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 the733 * 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 the764 * 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 human774 * readable form or not.775 * @param cchTab The tab size.776 * @param pDiff Where to write the diff.777 */778 54 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol, 779 55 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 58 RT_C_DECLS_END 59 785 60 #endif 786 61 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 code853 * @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 code885 * @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 < 11010 || 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 attempting1041 * 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 options1067 * 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 options1100 * 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 the1134 * 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].pszPattern1246 && pSettings->paPairs[iPair].pszOptions)1247 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);1248 else1249 rc = VERR_NO_MEMORY;1250 if (RT_SUCCESS(rc))1251 pSettings->cPairs = iPair + 1;1252 else1253 {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 else1295 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 code1318 * @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 or1344 * directory.1345 *1346 * This will look for .scm-settings files from the root and down to the1347 * 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 stack1351 * object.1352 * @param pBaseSettings The base settings we inherit from (globals1353 * 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 setting1378 * 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 else1399 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 stack1407 * 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 even1424 * if no settings file was found.1425 *1426 * @returns IPRT status code.1427 * @param ppSettingsStack The pointer to the pointer to the top stack1428 * 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 stack1461 * 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 stack1482 * 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 to1497 * 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 we1550 * 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(pState1565 ? "%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_LIBSVN1580 1581 #ifdef SCM_WITHOUT_LIBSVN1582 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 #else1597 int rc = RTPathAppend(pszDst, cchDst, "svn");1598 #endif1599 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 return1611 * "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_WINDOWS1619 const char *pszEnvVar = RTEnvGet("Path");1620 #else1621 const char *pszEnvVar = RTEnvGet("PATH");1622 #endif1623 if (pszPath)1624 {1625 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1626 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1627 #else1628 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1629 #endif1630 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 else1665 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_LIBSVN1705 /*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 #else1714 NOREF(pState);1715 #endif1716 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 this1730 * 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_LIBSVN1750 /*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 == 31777 && !memcmp(pchLine, "END", 3))1778 break;1779 size_t cchKey;1780 if ( cchLine < 31781 || pchLine[0] != 'K'1782 || pchLine[1] != ' '1783 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)1784 || cchKey == 01785 || 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 with1794 * 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 == cchName1804 && !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 < 31820 || 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 the1832 * value into it. Otherwise skip this value and continue1833 * searching.1834 */1835 if (fMatch)1836 {1837 if (!ppszValue)1838 rc = VINF_SUCCESS;1839 else1840 {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 else1849 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 #else1875 NOREF(pState);1876 #endif1877 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 else1905 {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].pszName1930 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )1931 pState->cSvnPropChanges = i + 1;1932 else1933 {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 changes1962 * 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 else1974 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 changes1985 * should be applied.1986 */1987 static int scmSvnApplyChanges(PSCMRWSTATE pState)1988 {1989 #ifdef SCM_WITHOUT_LIBSVN1990 /*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_NORMAL2022 || 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 #else2044 return VERR_NOT_IMPLEMENTED;2045 #endif2046 }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 == 02073 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )2074 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);2075 else2076 {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 else2116 {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 != enmDesiredEol2172 && 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->fSetSvnEol2186 && 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 else2196 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 #else2222 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");2223 #endif2224 }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 line2254 * 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->fStripTrailingLines2266 && !pSettings->fForceTrailingLine2267 && !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->fStripTrailingLines2280 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))2281 {2282 while ( cLinesNew > 12283 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))2284 cLinesNew--;2285 }2286 2287 if ( pSettings->fForceTrailingLine2288 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))2289 cLinesNew++;2290 2291 bool fFixMissingEol = pSettings->fForceFinalEol2292 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;2293 2294 if ( !fFixMissingEol2295 && 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 else2315 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->fSetSvnExecutable2334 || !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->fSetSvnKeywords2360 || !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 else2374 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 else2383 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 * @todo2425 *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 * @todo2444 *2445 * Ideas for C/C++:2446 * - space after if, while, for, switch2447 * - spaces in for (i=0;i<x;i++)2448 * - complex conditional, bird style.2449 * - remove unnecessary parentheses.2450 * - sort defined RT_OS_*|| and RT_ARCH2451 * - 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 to2482 * RTStrSimplePatternMultiMatch.)2483 * @param pBaseSettings The base settings to use. It's OK to modify2484 * 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->pszFilterFiles2493 && *pBaseSettings->pszFilterFiles2494 && !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->pszFilterOutFiles2500 && *pBaseSettings->pszFilterOutFiles2501 && ( 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->fOnlySvnFiles2508 && !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 the2555 * rewriters, switching the two streams around when something is2556 * 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 else2603 {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 else2623 scmSvnDisplayChanges(pState);2624 }2625 2626 if (!fModified && !pState->cSvnPropChanges)2627 ScmVerbose(pState, 3, "no change\n", pszFilename);2628 }2629 else2630 RTMsgError("%s: stream error %Rrc\n", pszFilename);2631 ScmStreamDelete(&Stream3);2632 }2633 else2634 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2635 ScmStreamDelete(&Stream2);2636 }2637 else2638 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2639 }2640 else2641 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);2642 }2643 else2644 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 the2654 * 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 to2660 * 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 on2716 * entry. This ends with a dot. This is passed2717 * along when recursing in order to save stack space2718 * and avoid needless copying.2719 * @param cchDir Length of our path in pszbuf.2720 * @param pEntry Directory entry buffer. This is also passed2721 * 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 restrict2724 * 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 else2779 RTMsgError("RTDirRead -> %Rrc\n", rc);2780 break;2781 }2782 2783 /* Skip '.' and '..'. */2784 if ( pEntry->szName[0] == '.'2785 && ( pEntry->cbName == 12786 || ( pEntry->cbName == 22787 && pEntry->szName[1] == '.')))2788 continue;2789 2790 /* Enter it into the buffer so we've got a full name to work2791 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.pszFilterOutDirs2819 || !*pSettingsStack->Base.pszFilterOutDirs2820 || ( !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 to2848 * 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 else2862 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 code2871 * @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 else2897 {2898 RTMsgError("RTPathFilename: NULL\n");2899 rc = VERR_IS_A_DIRECTORY;2900 }2901 }2902 else2903 rc = scmProcessDirTree(szBuf, pSettingsStack);2904 2905 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);2906 Assert(pPopped == pSettings);2907 scmSettingsDestroy(pSettings);2908 }2909 else2910 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);2911 }2912 else2913 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 only2936 * 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-") != NULL2992 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL2993 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL2994 );2995 if (fAdvanceTwo)2996 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);2997 else2998 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 else3003 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 21 21 #include <iprt/assert.h> 22 22 #include <iprt/ctype.h> 23 //#include <iprt/dir.h>24 //#include <iprt/env.h>25 23 #include <iprt/file.h> 26 24 #include <iprt/err.h> 27 //#include <iprt/getopt.h>28 //#include <iprt/initterm.h>29 25 #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>35 26 #include <iprt/string.h> 36 27
Note:
See TracChangeset
for help on using the changeset viewer.