Changeset 40528 in vbox for trunk/src/bldprogs
- Timestamp:
- Mar 19, 2012 11:04:34 AM (13 years ago)
- Location:
- trunk/src/bldprogs
- Files:
-
- 2 edited
- 2 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bldprogs/Makefile.kmk
r34484 r40528 36 36 PROGRAMS += scm 37 37 scm_TEMPLATE = VBOXR3EXE 38 scm_SOURCES = scm.cpp 38 scm_SOURCES = \ 39 scm.cpp \ 40 scmstream.cpp 39 41 scm_LIBS = \ 40 42 $(LIB_RUNTIME) -
trunk/src/bldprogs/scm.cpp
r39612 r40528 5 5 6 6 /* 7 * Copyright (C) 2010 Oracle Corporation7 * Copyright (C) 2010-2012 Oracle Corporation 8 8 * 9 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 35 35 #include <iprt/string.h> 36 36 37 #include "scmstream.h" 38 37 39 38 40 /******************************************************************************* … … 48 50 /** Pointer to const massager settings. */ 49 51 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; 50 51 /** End of line marker type. */52 typedef enum SCMEOL53 {54 SCMEOL_NONE = 0,55 SCMEOL_LF = 1,56 SCMEOL_CRLF = 257 } SCMEOL;58 /** Pointer to an end of line marker type. */59 typedef SCMEOL *PSCMEOL;60 61 /**62 * Line record.63 */64 typedef struct SCMSTREAMLINE65 {66 /** The offset of the line. */67 size_t off;68 /** The line length, excluding the LF character.69 * @todo This could be derived from the offset of the next line if that wasn't70 * so tedious. */71 size_t cch;72 /** The end of line marker type. */73 SCMEOL enmEol;74 } SCMSTREAMLINE;75 /** Pointer to a line record. */76 typedef SCMSTREAMLINE *PSCMSTREAMLINE;77 78 /**79 * Source code massager stream.80 */81 typedef struct SCMSTREAM82 {83 /** Pointer to the file memory. */84 char *pch;85 /** The current stream position. */86 size_t off;87 /** The current stream size. */88 size_t cb;89 /** The size of the memory pb points to. */90 size_t cbAllocated;91 92 /** Line records. */93 PSCMSTREAMLINE paLines;94 /** The current line. */95 size_t iLine;96 /** The current stream size given in lines. */97 size_t cLines;98 /** The sizeof the the memory backing paLines. */99 size_t cLinesAllocated;100 101 /** Set if write-only, clear if read-only. */102 bool fWriteOrRead;103 /** Set if the memory pb points to is from RTFileReadAll. */104 bool fFileMemory;105 /** Set if fully broken into lines. */106 bool fFullyLineated;107 108 /** Stream status code (IPRT). */109 int rc;110 } SCMSTREAM;111 /** Pointer to a SCM stream. */112 typedef SCMSTREAM *PSCMSTREAM;113 /** Pointer to a const SCM stream. */114 typedef SCMSTREAM const *PCSCMSTREAM;115 116 52 117 53 /** … … 500 436 }; 501 437 502 503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */504 505 506 /**507 * Initializes the stream structure.508 *509 * @param pStream The stream structure.510 * @param fWriteOrRead The value of the fWriteOrRead stream member.511 */512 static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)513 {514 pStream->pch = NULL;515 pStream->off = 0;516 pStream->cb = 0;517 pStream->cbAllocated = 0;518 519 pStream->paLines = NULL;520 pStream->iLine = 0;521 pStream->cLines = 0;522 pStream->cLinesAllocated = 0;523 524 pStream->fWriteOrRead = fWriteOrRead;525 pStream->fFileMemory = false;526 pStream->fFullyLineated = false;527 528 pStream->rc = VINF_SUCCESS;529 }530 531 /**532 * Initialize an input stream.533 *534 * @returns IPRT status code.535 * @param pStream The stream to initialize.536 * @param pszFilename The file to take the stream content from.537 */538 int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)539 {540 scmStreamInitInternal(pStream, false /*fWriteOrRead*/);541 542 void *pvFile;543 size_t cbFile;544 int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);545 if (RT_SUCCESS(rc))546 {547 pStream->pch = (char *)pvFile;548 pStream->cb = cbFile;549 pStream->cbAllocated = cbFile;550 pStream->fFileMemory = true;551 }552 return rc;553 }554 555 /**556 * Initialize an output stream.557 *558 * @returns IPRT status code559 * @param pStream The stream to initialize.560 * @param pRelatedStream Pointer to a related stream. NULL is fine.561 */562 int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)563 {564 scmStreamInitInternal(pStream, true /*fWriteOrRead*/);565 566 /* allocate stuff */567 size_t cbEstimate = pRelatedStream568 ? pRelatedStream->cb + pRelatedStream->cb / 10569 : _64K;570 cbEstimate = RT_ALIGN(cbEstimate, _4K);571 pStream->pch = (char *)RTMemAlloc(cbEstimate);572 if (pStream->pch)573 {574 size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated575 ? pRelatedStream->cLines + pRelatedStream->cLines / 10576 : cbEstimate / 24;577 cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);578 pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));579 if (pStream->paLines)580 {581 pStream->paLines[0].off = 0;582 pStream->paLines[0].cch = 0;583 pStream->paLines[0].enmEol = SCMEOL_NONE;584 pStream->cbAllocated = cbEstimate;585 pStream->cLinesAllocated = cLinesEstimate;586 return VINF_SUCCESS;587 }588 589 RTMemFree(pStream->pch);590 pStream->pch = NULL;591 }592 return pStream->rc = VERR_NO_MEMORY;593 }594 595 /**596 * Frees the resources associated with the stream.597 *598 * Nothing is happens to whatever the stream was initialized from or dumped to.599 *600 * @param pStream The stream to delete.601 */602 void ScmStreamDelete(PSCMSTREAM pStream)603 {604 if (pStream->pch)605 {606 if (pStream->fFileMemory)607 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);608 else609 RTMemFree(pStream->pch);610 pStream->pch = NULL;611 }612 pStream->cbAllocated = 0;613 614 if (pStream->paLines)615 {616 RTMemFree(pStream->paLines);617 pStream->paLines = NULL;618 }619 pStream->cLinesAllocated = 0;620 }621 622 /**623 * Get the stream status code.624 *625 * @returns IPRT status code.626 * @param pStream The stream.627 */628 int ScmStreamGetStatus(PCSCMSTREAM pStream)629 {630 return pStream->rc;631 }632 633 /**634 * Grows the buffer of a write stream.635 *636 * @returns IPRT status code.637 * @param pStream The stream. Must be in write mode.638 * @param cbAppending The minimum number of bytes to grow the buffer639 * with.640 */641 static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)642 {643 size_t cbAllocated = pStream->cbAllocated;644 cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);645 cbAllocated = RT_ALIGN(cbAllocated, 0x1000);646 void *pvNew;647 if (!pStream->fFileMemory)648 {649 pvNew = RTMemRealloc(pStream->pch, cbAllocated);650 if (!pvNew)651 return pStream->rc = VERR_NO_MEMORY;652 }653 else654 {655 pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);656 if (!pvNew)657 return pStream->rc = VERR_NO_MEMORY;658 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);659 pStream->fFileMemory = false;660 }661 pStream->pch = (char *)pvNew;662 pStream->cbAllocated = cbAllocated;663 664 return VINF_SUCCESS;665 }666 667 /**668 * Grows the line array of a stream.669 *670 * @returns IPRT status code.671 * @param pStream The stream.672 * @param iMinLine Minimum line number.673 */674 static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)675 {676 size_t cLinesAllocated = pStream->cLinesAllocated;677 cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);678 cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);679 void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));680 if (!pvNew)681 return pStream->rc = VERR_NO_MEMORY;682 683 pStream->paLines = (PSCMSTREAMLINE)pvNew;684 pStream->cLinesAllocated = cLinesAllocated;685 return VINF_SUCCESS;686 }687 688 /**689 * Rewinds the stream and sets the mode to read.690 *691 * @param pStream The stream.692 */693 void ScmStreamRewindForReading(PSCMSTREAM pStream)694 {695 pStream->off = 0;696 pStream->iLine = 0;697 pStream->fWriteOrRead = false;698 pStream->rc = VINF_SUCCESS;699 }700 701 /**702 * Rewinds the stream and sets the mode to write.703 *704 * @param pStream The stream.705 */706 void ScmStreamRewindForWriting(PSCMSTREAM pStream)707 {708 pStream->off = 0;709 pStream->iLine = 0;710 pStream->cLines = 0;711 pStream->fWriteOrRead = true;712 pStream->fFullyLineated = true;713 pStream->rc = VINF_SUCCESS;714 }715 716 /**717 * Checks if it's a text stream.718 *719 * Not 100% proof.720 *721 * @returns true if it probably is a text file, false if not.722 * @param pStream The stream. Write or read, doesn't matter.723 */724 bool ScmStreamIsText(PSCMSTREAM pStream)725 {726 if (RTStrEnd(pStream->pch, pStream->cb))727 return false;728 if (!pStream->cb)729 return false;730 return true;731 }732 733 /**734 * Performs an integrity check of the stream.735 *736 * @returns IPRT status code.737 * @param pStream The stream.738 */739 int ScmStreamCheckItegrity(PSCMSTREAM pStream)740 {741 /*742 * Perform sanity checks.743 */744 size_t const cbFile = pStream->cb;745 for (size_t iLine = 0; iLine < pStream->cLines; iLine++)746 {747 size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;748 AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);749 switch (pStream->paLines[iLine].enmEol)750 {751 case SCMEOL_LF:752 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);753 break;754 case SCMEOL_CRLF:755 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);756 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);757 break;758 case SCMEOL_NONE:759 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);760 break;761 default:762 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);763 }764 }765 return VINF_SUCCESS;766 }767 768 /**769 * Writes the stream to a file.770 *771 * @returns IPRT status code772 * @param pStream The stream.773 * @param pszFilenameFmt The filename format string.774 * @param ... Format arguments.775 */776 int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)777 {778 int rc;779 780 #ifdef RT_STRICT781 /*782 * Check that what we're going to write makes sense first.783 */784 rc = ScmStreamCheckItegrity(pStream);785 if (RT_FAILURE(rc))786 return rc;787 #endif788 789 /*790 * Do the actual writing.791 */792 RTFILE hFile;793 va_list va;794 va_start(va, pszFilenameFmt);795 rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);796 if (RT_SUCCESS(rc))797 {798 rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);799 RTFileClose(hFile);800 }801 return rc;802 }803 804 /**805 * Worker for ScmStreamGetLine that builds the line number index while parsing806 * the stream.807 *808 * @returns Same as SCMStreamGetLine.809 * @param pStream The stream. Must be in read mode.810 * @param pcchLine Where to return the line length.811 * @param penmEol Where to return the kind of end of line marker.812 */813 static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)814 {815 AssertReturn(!pStream->fWriteOrRead, NULL);816 if (RT_FAILURE(pStream->rc))817 return NULL;818 819 size_t off = pStream->off;820 size_t cb = pStream->cb;821 if (RT_UNLIKELY(off >= cb))822 {823 pStream->fFullyLineated = true;824 return NULL;825 }826 827 size_t iLine = pStream->iLine;828 if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))829 {830 int rc = scmStreamGrowLines(pStream, iLine);831 if (RT_FAILURE(rc))832 return NULL;833 }834 pStream->paLines[iLine].off = off;835 836 cb -= off;837 const char *pchRet = &pStream->pch[off];838 const char *pch = (const char *)memchr(pchRet, '\n', cb);839 if (RT_LIKELY(pch))840 {841 cb = pch - pchRet;842 pStream->off = off + cb + 1;843 if ( cb < 1844 || pch[-1] != '\r')845 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;846 else847 {848 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;849 cb--;850 }851 }852 else853 {854 pStream->off = off + cb;855 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;856 }857 *pcchLine = cb;858 pStream->paLines[iLine].cch = cb;859 pStream->cLines = pStream->iLine = ++iLine;860 861 return pchRet;862 }863 864 /**865 * Internal worker that delineates a stream.866 *867 * @returns IPRT status code.868 * @param pStream The stream. Caller must check that it is in869 * read mode.870 */871 static int scmStreamLineate(PSCMSTREAM pStream)872 {873 /* Save the stream position. */874 size_t const offSaved = pStream->off;875 size_t const iLineSaved = pStream->iLine;876 877 /* Get each line. */878 size_t cchLine;879 SCMEOL enmEol;880 while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))881 /* nothing */;882 Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);883 884 /* Restore the position */885 pStream->off = offSaved;886 pStream->iLine = iLineSaved;887 888 return pStream->rc;889 }890 891 /**892 * Get the current stream position as an byte offset.893 *894 * @returns The current byte offset895 * @param pStream The stream.896 */897 size_t ScmStreamTell(PSCMSTREAM pStream)898 {899 return pStream->off;900 }901 902 /**903 * Get the current stream position as a line number.904 *905 * @returns The current line (0-based).906 * @param pStream The stream.907 */908 size_t ScmStreamTellLine(PSCMSTREAM pStream)909 {910 return pStream->iLine;911 }912 913 /**914 * Get the current stream size in bytes.915 *916 * @returns Count of bytes.917 * @param pStream The stream.918 */919 size_t ScmStreamSize(PSCMSTREAM pStream)920 {921 return pStream->cb;922 }923 924 /**925 * Gets the number of lines in the stream.926 *927 * @returns The number of lines.928 * @param pStream The stream.929 */930 size_t ScmStreamCountLines(PSCMSTREAM pStream)931 {932 if (!pStream->fFullyLineated)933 scmStreamLineate(pStream);934 return pStream->cLines;935 }936 937 /**938 * Seeks to a given byte offset in the stream.939 *940 * @returns IPRT status code.941 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.942 * This is a temporary restriction.943 *944 * @param pStream The stream. Must be in read mode.945 * @param offAbsolute The offset to seek to. If this is beyond the946 * end of the stream, the position is set to the947 * end.948 */949 int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)950 {951 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);952 if (RT_FAILURE(pStream->rc))953 return pStream->rc;954 955 /* Must be fully delineated. (lazy bird) */956 if (RT_UNLIKELY(!pStream->fFullyLineated))957 {958 int rc = scmStreamLineate(pStream);959 if (RT_FAILURE(rc))960 return rc;961 }962 963 /* Ok, do the job. */964 if (offAbsolute < pStream->cb)965 {966 /** @todo Should do a binary search here, but I'm too darn lazy tonight. */967 pStream->off = ~(size_t)0;968 for (size_t i = 0; i < pStream->cLines; i++)969 {970 if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)971 {972 pStream->off = offAbsolute;973 pStream->iLine = i;974 if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)975 return pStream->rc = VERR_SEEK;976 break;977 }978 }979 AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);980 }981 else982 {983 pStream->off = pStream->cb;984 pStream->iLine = pStream->cLines;985 }986 return VINF_SUCCESS;987 }988 989 990 /**991 * Seeks a number of bytes relative to the current stream position.992 *993 * @returns IPRT status code.994 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.995 * This is a temporary restriction.996 *997 * @param pStream The stream. Must be in read mode.998 * @param offRelative The offset to seek to. A negative offset999 * rewinds and positive one fast forwards the1000 * stream. Will quietly stop at the beginning and1001 * end of the stream.1002 */1003 int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)1004 {1005 size_t offAbsolute;1006 if (offRelative >= 0)1007 offAbsolute = pStream->off + offRelative;1008 else if ((size_t)-offRelative <= pStream->off)1009 offAbsolute = pStream->off + offRelative;1010 else1011 offAbsolute = 0;1012 return ScmStreamSeekAbsolute(pStream, offAbsolute);1013 }1014 1015 /**1016 * Seeks to a given line in the stream.1017 *1018 * @returns IPRT status code.1019 *1020 * @param pStream The stream. Must be in read mode.1021 * @param iLine The line to seek to. If this is beyond the end1022 * of the stream, the position is set to the end.1023 */1024 int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)1025 {1026 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);1027 if (RT_FAILURE(pStream->rc))1028 return pStream->rc;1029 1030 /* Must be fully delineated. (lazy bird) */1031 if (RT_UNLIKELY(!pStream->fFullyLineated))1032 {1033 int rc = scmStreamLineate(pStream);1034 if (RT_FAILURE(rc))1035 return rc;1036 }1037 1038 /* Ok, do the job. */1039 if (iLine < pStream->cLines)1040 {1041 pStream->off = pStream->paLines[iLine].off;1042 pStream->iLine = iLine;1043 }1044 else1045 {1046 pStream->off = pStream->cb;1047 pStream->iLine = pStream->cLines;1048 }1049 return VINF_SUCCESS;1050 }1051 1052 /**1053 * Get a numbered line from the stream (changes the position).1054 *1055 * A line is always delimited by a LF character or the end of the stream. The1056 * delimiter is not included in returned line length, but instead returned via1057 * the @a penmEol indicator.1058 *1059 * @returns Pointer to the first character in the line, not NULL terminated.1060 * NULL if the end of the stream has been reached or some problem1061 * occurred.1062 *1063 * @param pStream The stream. Must be in read mode.1064 * @param iLine The line to get (0-based).1065 * @param pcchLine The length.1066 * @param penmEol Where to return the end of line type indicator.1067 */1068 static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)1069 {1070 AssertReturn(!pStream->fWriteOrRead, NULL);1071 if (RT_FAILURE(pStream->rc))1072 return NULL;1073 1074 /* Make sure it's fully delineated so we can use the index. */1075 if (RT_UNLIKELY(!pStream->fFullyLineated))1076 {1077 int rc = scmStreamLineate(pStream);1078 if (RT_FAILURE(rc))1079 return NULL;1080 }1081 1082 /* End of stream? */1083 if (RT_UNLIKELY(iLine >= pStream->cLines))1084 {1085 pStream->off = pStream->cb;1086 pStream->iLine = pStream->cLines;1087 return NULL;1088 }1089 1090 /* Get the data. */1091 const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];1092 *pcchLine = pStream->paLines[iLine].cch;1093 *penmEol = pStream->paLines[iLine].enmEol;1094 1095 /* update the stream position. */1096 pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;1097 pStream->iLine = iLine + 1;1098 1099 return pchRet;1100 }1101 1102 /**1103 * Get a line from the stream.1104 *1105 * A line is always delimited by a LF character or the end of the stream. The1106 * delimiter is not included in returned line length, but instead returned via1107 * the @a penmEol indicator.1108 *1109 * @returns Pointer to the first character in the line, not NULL terminated.1110 * NULL if the end of the stream has been reached or some problem1111 * occurred.1112 *1113 * @param pStream The stream. Must be in read mode.1114 * @param pcchLine The length.1115 * @param penmEol Where to return the end of line type indicator.1116 */1117 static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)1118 {1119 /** @todo this doesn't work when pStream->off !=1120 * pStream->paLines[pStream->iLine-1].pff. */1121 if (!pStream->fFullyLineated)1122 return scmStreamGetLineInternal(pStream, pcchLine, penmEol);1123 return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);1124 }1125 1126 /**1127 * Reads @a cbToRead bytes into @a pvBuf.1128 *1129 * Will fail if end of stream is encountered before the entire read has been1130 * completed.1131 *1132 * @returns IPRT status code.1133 * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream1134 * position will be unchanged.1135 *1136 * @param pStream The stream. Must be in read mode.1137 * @param pvBuf The buffer to read into.1138 * @param cbToRead The number of bytes to read.1139 */1140 static int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)1141 {1142 AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);1143 if (RT_FAILURE(pStream->rc))1144 return pStream->rc;1145 1146 /* If there isn't enough stream left, fail already. */1147 if (RT_UNLIKELY(pStream->cb - pStream->cb < cbToRead))1148 return VERR_EOF;1149 1150 /* Copy the data and simply seek to the new stream position. */1151 memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);1152 return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);1153 }1154 1155 /**1156 * Checks if the given line is empty or full of white space.1157 *1158 * @returns true if white space only, false if not (or if non-existant).1159 * @param pStream The stream. Must be in read mode.1160 * @param iLine The line in question.1161 */1162 static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)1163 {1164 SCMEOL enmEol;1165 size_t cchLine;1166 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);1167 if (!pchLine)1168 return false;1169 while (cchLine && RT_C_IS_SPACE(*pchLine))1170 pchLine++, cchLine--;1171 return cchLine == 0;1172 }1173 1174 /**1175 * Try figure out the end of line style of the give stream.1176 *1177 * @returns Most likely end of line style.1178 * @param pStream The stream.1179 */1180 SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)1181 {1182 SCMEOL enmEol;1183 if (pStream->cLines > 0)1184 enmEol = pStream->paLines[0].enmEol;1185 else if (pStream->cb == 0)1186 enmEol = SCMEOL_NONE;1187 else1188 {1189 const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);1190 if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')1191 enmEol = SCMEOL_CRLF;1192 else1193 enmEol = SCMEOL_LF;1194 }1195 1196 if (enmEol == SCMEOL_NONE)1197 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1198 enmEol = SCMEOL_CRLF;1199 #else1200 enmEol = SCMEOL_LF;1201 #endif1202 return enmEol;1203 }1204 1205 /**1206 * Get the end of line indicator type for a line.1207 *1208 * @returns The EOL indicator. If the line isn't found, the default EOL1209 * indicator is return.1210 * @param pStream The stream.1211 * @param iLine The line (0-base).1212 */1213 SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)1214 {1215 SCMEOL enmEol;1216 if (iLine < pStream->cLines)1217 enmEol = pStream->paLines[iLine].enmEol;1218 else1219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1220 enmEol = SCMEOL_CRLF;1221 #else1222 enmEol = SCMEOL_LF;1223 #endif1224 return enmEol;1225 }1226 1227 /**1228 * Appends a line to the stream.1229 *1230 * @returns IPRT status code.1231 * @param pStream The stream. Must be in write mode.1232 * @param pchLine Pointer to the line.1233 * @param cchLine Line length.1234 * @param enmEol Which end of line indicator to use.1235 */1236 int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)1237 {1238 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1239 if (RT_FAILURE(pStream->rc))1240 return pStream->rc;1241 1242 /*1243 * Make sure the previous line has a new-line indicator.1244 */1245 size_t off = pStream->off;1246 size_t iLine = pStream->iLine;1247 if (RT_UNLIKELY( iLine != 01248 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))1249 {1250 AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);1251 SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);1252 if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))1253 {1254 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);1255 if (RT_FAILURE(rc))1256 return rc;1257 }1258 if (enmEol2 == SCMEOL_LF)1259 pStream->pch[off++] = '\n';1260 else1261 {1262 pStream->pch[off++] = '\r';1263 pStream->pch[off++] = '\n';1264 }1265 pStream->paLines[iLine - 1].enmEol = enmEol2;1266 pStream->paLines[iLine].off = off;1267 pStream->off = off;1268 pStream->cb = off;1269 }1270 1271 /*1272 * Ensure we've got sufficient buffer space.1273 */1274 if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))1275 {1276 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);1277 if (RT_FAILURE(rc))1278 return rc;1279 }1280 1281 /*1282 * Add a line record.1283 */1284 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))1285 {1286 int rc = scmStreamGrowLines(pStream, iLine);1287 if (RT_FAILURE(rc))1288 return rc;1289 }1290 1291 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine;1292 pStream->paLines[iLine].enmEol = enmEol;1293 1294 iLine++;1295 pStream->cLines = iLine;1296 pStream->iLine = iLine;1297 1298 /*1299 * Copy the line1300 */1301 memcpy(&pStream->pch[off], pchLine, cchLine);1302 off += cchLine;1303 if (enmEol == SCMEOL_LF)1304 pStream->pch[off++] = '\n';1305 else if (enmEol == SCMEOL_CRLF)1306 {1307 pStream->pch[off++] = '\r';1308 pStream->pch[off++] = '\n';1309 }1310 pStream->off = off;1311 pStream->cb = off;1312 1313 /*1314 * Start a new line.1315 */1316 pStream->paLines[iLine].off = off;1317 pStream->paLines[iLine].cch = 0;1318 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1319 1320 return VINF_SUCCESS;1321 }1322 1323 /**1324 * Writes to the stream.1325 *1326 * @returns IPRT status code1327 * @param pStream The stream. Must be in write mode.1328 * @param pchBuf What to write.1329 * @param cchBuf How much to write.1330 */1331 int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)1332 {1333 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1334 if (RT_FAILURE(pStream->rc))1335 return pStream->rc;1336 1337 /*1338 * Ensure we've got sufficient buffer space.1339 */1340 size_t off = pStream->off;1341 if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))1342 {1343 int rc = scmStreamGrowBuffer(pStream, cchBuf);1344 if (RT_FAILURE(rc))1345 return rc;1346 }1347 1348 /*1349 * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.1350 */1351 size_t iLine = pStream->iLine;1352 if (RT_UNLIKELY( iLine > 01353 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))1354 {1355 iLine--;1356 pStream->cLines = iLine;1357 pStream->iLine = iLine;1358 }1359 1360 /*1361 * Deal with lines.1362 */1363 const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);1364 if (!pchLF)1365 pStream->paLines[iLine].cch += cchBuf;1366 else1367 {1368 const char *pchLine = pchBuf;1369 for (;;)1370 {1371 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))1372 {1373 int rc = scmStreamGrowLines(pStream, iLine);1374 if (RT_FAILURE(rc))1375 {1376 iLine = pStream->iLine;1377 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off;1378 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1379 return rc;1380 }1381 }1382 1383 size_t cchLine = pchLF - pchLine;1384 if ( cchLine1385 ? pchLF[-1] != '\r'1386 : !pStream->paLines[iLine].cch1387 || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')1388 pStream->paLines[iLine].enmEol = SCMEOL_LF;1389 else1390 {1391 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;1392 cchLine--;1393 }1394 pStream->paLines[iLine].cch += cchLine;1395 1396 iLine++;1397 size_t offBuf = pchLF + 1 - pchBuf;1398 pStream->paLines[iLine].off = off + offBuf;1399 pStream->paLines[iLine].cch = 0;1400 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1401 1402 size_t cchLeft = cchBuf - offBuf;1403 pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);1404 if (!pchLF)1405 {1406 pStream->paLines[iLine].cch = cchLeft;1407 break;1408 }1409 }1410 1411 pStream->iLine = iLine;1412 pStream->cLines = iLine;1413 }1414 1415 /*1416 * Copy the data and update position and size.1417 */1418 memcpy(&pStream->pch[off], pchBuf, cchBuf);1419 off += cchBuf;1420 pStream->off = off;1421 pStream->cb = off;1422 1423 return VINF_SUCCESS;1424 }1425 1426 /**1427 * Write a character to the stream.1428 *1429 * @returns IPRT status code1430 * @param pStream The stream. Must be in write mode.1431 * @param pchBuf What to write.1432 * @param cchBuf How much to write.1433 */1434 int ScmStreamPutCh(PSCMSTREAM pStream, char ch)1435 {1436 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1437 if (RT_FAILURE(pStream->rc))1438 return pStream->rc;1439 1440 /*1441 * Only deal with the simple cases here, use ScmStreamWrite for the1442 * annoying stuff.1443 */1444 size_t off = pStream->off;1445 if ( ch == '\n'1446 || RT_UNLIKELY(off + 1 > pStream->cbAllocated))1447 return ScmStreamWrite(pStream, &ch, 1);1448 1449 /*1450 * Just append it.1451 */1452 pStream->pch[off] = ch;1453 pStream->off = off + 1;1454 pStream->paLines[pStream->iLine].cch++;1455 1456 return VINF_SUCCESS;1457 }1458 1459 /**1460 * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.1461 *1462 * The stream positions will be used and changed in both streams.1463 *1464 * @returns IPRT status code.1465 * @param pDst The destination stream. Must be in write mode.1466 * @param cLines The number of lines. (0 is accepted.)1467 * @param pSrc The source stream. Must be in read mode.1468 */1469 int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)1470 {1471 AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);1472 if (RT_FAILURE(pDst->rc))1473 return pDst->rc;1474 1475 AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);1476 if (RT_FAILURE(pSrc->rc))1477 return pSrc->rc;1478 1479 while (cLines-- > 0)1480 {1481 SCMEOL enmEol;1482 size_t cchLine;1483 const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);1484 if (!pchLine)1485 return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);1486 1487 int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);1488 if (RT_FAILURE(rc))1489 return rc;1490 }1491 1492 return VINF_SUCCESS;1493 }1494 438 1495 439 /* -=-=-=-=-=- diff -=-=-=-=-=- */ -
trunk/src/bldprogs/scmstream.cpp
r40523 r40528 1 1 /* $Id$ */ 2 2 /** @file 3 * IPRT Testcase / Tool - Source Code Massager .3 * IPRT Testcase / Tool - Source Code Massager Stream Code. 4 4 */ 5 5 6 6 /* 7 * Copyright (C) 2010 Oracle Corporation7 * Copyright (C) 2010-2012 Oracle Corporation 8 8 * 9 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 21 21 #include <iprt/assert.h> 22 22 #include <iprt/ctype.h> 23 #include <iprt/dir.h>24 #include <iprt/env.h>23 //#include <iprt/dir.h> 24 //#include <iprt/env.h> 25 25 #include <iprt/file.h> 26 26 #include <iprt/err.h> 27 #include <iprt/getopt.h>28 #include <iprt/initterm.h>27 //#include <iprt/getopt.h> 28 //#include <iprt/initterm.h> 29 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>34 #include <iprt/stream.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 35 #include <iprt/string.h> 36 36 37 38 /******************************************************************************* 39 * Defined Constants And Macros * 40 *******************************************************************************/ 41 /** The name of the settings files. */ 42 #define SCM_SETTINGS_FILENAME ".scm-settings" 43 44 45 /******************************************************************************* 46 * Structures and Typedefs * 47 *******************************************************************************/ 48 /** Pointer to const massager settings. */ 49 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; 50 51 /** End of line marker type. */ 52 typedef enum SCMEOL 53 { 54 SCMEOL_NONE = 0, 55 SCMEOL_LF = 1, 56 SCMEOL_CRLF = 2 57 } SCMEOL; 58 /** Pointer to an end of line marker type. */ 59 typedef SCMEOL *PSCMEOL; 60 61 /** 62 * Line record. 63 */ 64 typedef struct SCMSTREAMLINE 65 { 66 /** The offset of the line. */ 67 size_t off; 68 /** The line length, excluding the LF character. 69 * @todo This could be derived from the offset of the next line if that wasn't 70 * so tedious. */ 71 size_t cch; 72 /** The end of line marker type. */ 73 SCMEOL enmEol; 74 } SCMSTREAMLINE; 75 /** Pointer to a line record. */ 76 typedef SCMSTREAMLINE *PSCMSTREAMLINE; 77 78 /** 79 * Source code massager stream. 80 */ 81 typedef struct SCMSTREAM 82 { 83 /** Pointer to the file memory. */ 84 char *pch; 85 /** The current stream position. */ 86 size_t off; 87 /** The current stream size. */ 88 size_t cb; 89 /** The size of the memory pb points to. */ 90 size_t cbAllocated; 91 92 /** Line records. */ 93 PSCMSTREAMLINE paLines; 94 /** The current line. */ 95 size_t iLine; 96 /** The current stream size given in lines. */ 97 size_t cLines; 98 /** The sizeof the the memory backing paLines. */ 99 size_t cLinesAllocated; 100 101 /** Set if write-only, clear if read-only. */ 102 bool fWriteOrRead; 103 /** Set if the memory pb points to is from RTFileReadAll. */ 104 bool fFileMemory; 105 /** Set if fully broken into lines. */ 106 bool fFullyLineated; 107 108 /** Stream status code (IPRT). */ 109 int rc; 110 } SCMSTREAM; 111 /** Pointer to a SCM stream. */ 112 typedef SCMSTREAM *PSCMSTREAM; 113 /** Pointer to a const SCM stream. */ 114 typedef SCMSTREAM const *PCSCMSTREAM; 115 116 117 /** 118 * SVN property. 119 */ 120 typedef struct SCMSVNPROP 121 { 122 /** The property. */ 123 char *pszName; 124 /** The value. 125 * When used to record updates, this can be set to NULL to trigger the 126 * deletion of the property. */ 127 char *pszValue; 128 } SCMSVNPROP; 129 /** Pointer to a SVN property. */ 130 typedef SCMSVNPROP *PSCMSVNPROP; 131 /** Pointer to a const SVN property. */ 132 typedef SCMSVNPROP const *PCSCMSVNPROP; 133 134 135 /** 136 * Rewriter state. 137 */ 138 typedef struct SCMRWSTATE 139 { 140 /** The filename. */ 141 const char *pszFilename; 142 /** Set after the printing the first verbose message about a file under 143 * rewrite. */ 144 bool fFirst; 145 /** The number of SVN property changes. */ 146 size_t cSvnPropChanges; 147 /** Pointer to an array of SVN property changes. */ 148 PSCMSVNPROP paSvnPropChanges; 149 } SCMRWSTATE; 150 /** Pointer to the rewriter state. */ 151 typedef SCMRWSTATE *PSCMRWSTATE; 152 153 /** 154 * A rewriter. 155 * 156 * This works like a stream editor, reading @a pIn, modifying it and writing it 157 * to @a pOut. 158 * 159 * @returns true if any changes were made, false if not. 160 * @param pIn The input stream. 161 * @param pOut The output stream. 162 * @param pSettings The settings. 163 */ 164 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 165 166 167 /** 168 * Configuration entry. 169 */ 170 typedef struct SCMCFGENTRY 171 { 172 /** Number of rewriters. */ 173 size_t cRewriters; 174 /** Pointer to an array of rewriters. */ 175 PFNSCMREWRITER const *papfnRewriter; 176 /** File pattern (simple). */ 177 const char *pszFilePattern; 178 } SCMCFGENTRY; 179 typedef SCMCFGENTRY *PSCMCFGENTRY; 180 typedef SCMCFGENTRY const *PCSCMCFGENTRY; 181 182 183 /** 184 * Diff state. 185 */ 186 typedef struct SCMDIFFSTATE 187 { 188 size_t cDiffs; 189 const char *pszFilename; 190 191 PSCMSTREAM pLeft; 192 PSCMSTREAM pRight; 193 194 /** Whether to ignore end of line markers when diffing. */ 195 bool fIgnoreEol; 196 /** Whether to ignore trailing whitespace. */ 197 bool fIgnoreTrailingWhite; 198 /** Whether to ignore leading whitespace. */ 199 bool fIgnoreLeadingWhite; 200 /** Whether to print special characters in human readable form or not. */ 201 bool fSpecialChars; 202 /** The tab size. */ 203 size_t cchTab; 204 /** Where to push the diff. */ 205 PRTSTREAM pDiff; 206 } SCMDIFFSTATE; 207 /** Pointer to a diff state. */ 208 typedef SCMDIFFSTATE *PSCMDIFFSTATE; 209 210 /** 211 * Source Code Massager Settings. 212 */ 213 typedef struct SCMSETTINGSBASE 214 { 215 bool fConvertEol; 216 bool fConvertTabs; 217 bool fForceFinalEol; 218 bool fForceTrailingLine; 219 bool fStripTrailingBlanks; 220 bool fStripTrailingLines; 221 /** Only process files that are part of a SVN working copy. */ 222 bool fOnlySvnFiles; 223 /** Only recurse into directories containing an .svn dir. */ 224 bool fOnlySvnDirs; 225 /** Set svn:eol-style if missing or incorrect. */ 226 bool fSetSvnEol; 227 /** Set svn:executable according to type (unusually this means deleting it). */ 228 bool fSetSvnExecutable; 229 /** Set svn:keyword if completely or partially missing. */ 230 bool fSetSvnKeywords; 231 /** */ 232 unsigned cchTab; 233 /** Only consider files matching these patterns. This is only applied to the 234 * base names. */ 235 char *pszFilterFiles; 236 /** Filter out files matching the following patterns. This is applied to base 237 * names as well as the absolute paths. */ 238 char *pszFilterOutFiles; 239 /** Filter out directories matching the following patterns. This is applied 240 * to base names as well as the absolute paths. All absolute paths ends with a 241 * slash and dot ("/."). */ 242 char *pszFilterOutDirs; 243 } SCMSETTINGSBASE; 244 /** Pointer to massager settings. */ 245 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE; 246 247 /** 248 * Option identifiers. 249 * 250 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set & 251 * clear. So, the option setting a flag (boolean) will have an even 252 * number and the one clearing it will have an odd number. 253 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE. 254 */ 255 typedef enum SCMOPT 256 { 257 SCMOPT_CONVERT_EOL = 10000, 258 SCMOPT_NO_CONVERT_EOL, 259 SCMOPT_CONVERT_TABS, 260 SCMOPT_NO_CONVERT_TABS, 261 SCMOPT_FORCE_FINAL_EOL, 262 SCMOPT_NO_FORCE_FINAL_EOL, 263 SCMOPT_FORCE_TRAILING_LINE, 264 SCMOPT_NO_FORCE_TRAILING_LINE, 265 SCMOPT_STRIP_TRAILING_BLANKS, 266 SCMOPT_NO_STRIP_TRAILING_BLANKS, 267 SCMOPT_STRIP_TRAILING_LINES, 268 SCMOPT_NO_STRIP_TRAILING_LINES, 269 SCMOPT_ONLY_SVN_DIRS, 270 SCMOPT_NOT_ONLY_SVN_DIRS, 271 SCMOPT_ONLY_SVN_FILES, 272 SCMOPT_NOT_ONLY_SVN_FILES, 273 SCMOPT_SET_SVN_EOL, 274 SCMOPT_DONT_SET_SVN_EOL, 275 SCMOPT_SET_SVN_EXECUTABLE, 276 SCMOPT_DONT_SET_SVN_EXECUTABLE, 277 SCMOPT_SET_SVN_KEYWORDS, 278 SCMOPT_DONT_SET_SVN_KEYWORDS, 279 SCMOPT_TAB_SIZE, 280 SCMOPT_FILTER_OUT_DIRS, 281 SCMOPT_FILTER_FILES, 282 SCMOPT_FILTER_OUT_FILES, 283 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES, 284 // 285 SCMOPT_DIFF_IGNORE_EOL, 286 SCMOPT_DIFF_NO_IGNORE_EOL, 287 SCMOPT_DIFF_IGNORE_SPACE, 288 SCMOPT_DIFF_NO_IGNORE_SPACE, 289 SCMOPT_DIFF_IGNORE_LEADING_SPACE, 290 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, 291 SCMOPT_DIFF_IGNORE_TRAILING_SPACE, 292 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, 293 SCMOPT_DIFF_SPECIAL_CHARS, 294 SCMOPT_DIFF_NO_SPECIAL_CHARS, 295 SCMOPT_END 296 } SCMOPT; 297 298 299 /** 300 * File/dir pattern + options. 301 */ 302 typedef struct SCMPATRNOPTPAIR 303 { 304 char *pszPattern; 305 char *pszOptions; 306 } SCMPATRNOPTPAIR; 307 /** Pointer to a pattern + option pair. */ 308 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR; 309 310 311 /** Pointer to a settings set. */ 312 typedef struct SCMSETTINGS *PSCMSETTINGS; 313 /** 314 * Settings set. 315 * 316 * This structure is constructed from the command line arguments or any 317 * .scm-settings file found in a directory we recurse into. When recursing in 318 * and out of a directory, we push and pop a settings set for it. 319 * 320 * The .scm-settings file has two kinds of setttings, first there are the 321 * unqualified base settings and then there are the settings which applies to a 322 * set of files or directories. The former are lines with command line options. 323 * For the latter, the options are preceded by a string pattern and a colon. 324 * The pattern specifies which files (and/or directories) the options applies 325 * to. 326 * 327 * We parse the base options into the Base member and put the others into the 328 * paPairs array. 329 */ 330 typedef struct SCMSETTINGS 331 { 332 /** Pointer to the setting file below us in the stack. */ 333 PSCMSETTINGS pDown; 334 /** Pointer to the setting file above us in the stack. */ 335 PSCMSETTINGS pUp; 336 /** File/dir patterns and their options. */ 337 PSCMPATRNOPTPAIR paPairs; 338 /** The number of entires in paPairs. */ 339 uint32_t cPairs; 340 /** The base settings that was read out of the file. */ 341 SCMSETTINGSBASE Base; 342 } SCMSETTINGS; 343 /** Pointer to a const settings set. */ 344 typedef SCMSETTINGS const *PCSCMSETTINGS; 345 346 347 /******************************************************************************* 348 * Internal Functions * 349 *******************************************************************************/ 350 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 351 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 352 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 353 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 354 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 355 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 356 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 358 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 359 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 360 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 361 362 363 /******************************************************************************* 364 * Global Variables * 365 *******************************************************************************/ 366 static const char g_szProgName[] = "scm"; 367 static const char *g_pszChangedSuff = ""; 368 static const char g_szTabSpaces[16+1] = " "; 369 static bool g_fDryRun = true; 370 static bool g_fDiffSpecialChars = true; 371 static bool g_fDiffIgnoreEol = false; 372 static bool g_fDiffIgnoreLeadingWS = false; 373 static bool g_fDiffIgnoreTrailingWS = false; 374 static int g_iVerbosity = 2;//99; //0; 375 376 /** The global settings. */ 377 static SCMSETTINGSBASE const g_Defaults = 378 { 379 /* .fConvertEol = */ true, 380 /* .fConvertTabs = */ true, 381 /* .fForceFinalEol = */ true, 382 /* .fForceTrailingLine = */ false, 383 /* .fStripTrailingBlanks = */ true, 384 /* .fStripTrailingLines = */ true, 385 /* .fOnlySvnFiles = */ false, 386 /* .fOnlySvnDirs = */ false, 387 /* .fSetSvnEol = */ false, 388 /* .fSetSvnExecutable = */ false, 389 /* .fSetSvnKeywords = */ false, 390 /* .cchTab = */ 8, 391 /* .pszFilterFiles = */ (char *)"", 392 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log", 393 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS", 394 }; 395 396 /** Option definitions for the base settings. */ 397 static RTGETOPTDEF g_aScmOpts[] = 398 { 399 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING }, 400 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING }, 401 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING }, 402 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING }, 403 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING }, 404 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING }, 405 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING }, 406 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING }, 407 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING }, 408 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING }, 409 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING }, 410 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING }, 411 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING }, 412 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING }, 413 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING }, 414 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING }, 415 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING }, 416 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING }, 417 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING }, 418 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING }, 419 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING }, 420 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING }, 421 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 }, 422 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING }, 423 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING }, 424 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING }, 425 }; 426 427 /** Consider files matching the following patterns (base names only). */ 428 static const char *g_pszFileFilter = NULL; 429 430 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] = 431 { 432 rewrite_SvnNoExecutable, 433 rewrite_Makefile_kup 434 }; 435 436 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] = 437 { 438 rewrite_ForceNativeEol, 439 rewrite_StripTrailingBlanks, 440 rewrite_AdjustTrailingLines, 441 rewrite_SvnNoExecutable, 442 rewrite_SvnKeywords, 443 rewrite_Makefile_kmk 444 }; 445 446 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] = 447 { 448 rewrite_ForceNativeEol, 449 rewrite_ExpandTabs, 450 rewrite_StripTrailingBlanks, 451 rewrite_AdjustTrailingLines, 452 rewrite_SvnNoExecutable, 453 rewrite_SvnKeywords, 454 rewrite_C_and_CPP 455 }; 456 457 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] = 458 { 459 rewrite_ForceNativeEol, 460 rewrite_ExpandTabs, 461 rewrite_StripTrailingBlanks, 462 rewrite_AdjustTrailingLines, 463 rewrite_SvnNoExecutable, 464 rewrite_C_and_CPP 465 }; 466 467 static PFNSCMREWRITER const g_aRewritersFor_RC[] = 468 { 469 rewrite_ForceNativeEol, 470 rewrite_ExpandTabs, 471 rewrite_StripTrailingBlanks, 472 rewrite_AdjustTrailingLines, 473 rewrite_SvnNoExecutable, 474 rewrite_SvnKeywords 475 }; 476 477 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] = 478 { 479 rewrite_ForceLF, 480 rewrite_ExpandTabs, 481 rewrite_StripTrailingBlanks 482 }; 483 484 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] = 485 { 486 rewrite_ForceCRLF, 487 rewrite_ExpandTabs, 488 rewrite_StripTrailingBlanks 489 }; 490 491 static SCMCFGENTRY const g_aConfigs[] = 492 { 493 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" }, 494 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" }, 495 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" }, 496 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" }, 497 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" }, 498 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" }, 499 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" }, 500 }; 501 502 503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */ 37 #include "scmstream.h" 504 38 505 39 … … 1066 600 * @param penmEol Where to return the end of line type indicator. 1067 601 */ 1068 staticconst char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)602 const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol) 1069 603 { 1070 604 AssertReturn(!pStream->fWriteOrRead, NULL); … … 1115 649 * @param penmEol Where to return the end of line type indicator. 1116 650 */ 1117 staticconst char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)651 const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol) 1118 652 { 1119 653 /** @todo this doesn't work when pStream->off != … … 1138 672 * @param cbToRead The number of bytes to read. 1139 673 */ 1140 staticint ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)674 int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead) 1141 675 { 1142 676 AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED); … … 1160 694 * @param iLine The line in question. 1161 695 */ 1162 staticbool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)696 bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine) 1163 697 { 1164 698 SCMEOL enmEol; … … 1493 1027 } 1494 1028 1495 /* -=-=-=-=-=- diff -=-=-=-=-=- */1496 1497 1498 /**1499 * Prints a range of lines with a prefix.1500 *1501 * @param pState The diff state.1502 * @param chPrefix The prefix.1503 * @param pStream The stream to get the lines from.1504 * @param iLine The first line.1505 * @param cLines The number of lines.1506 */1507 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)1508 {1509 while (cLines-- > 0)1510 {1511 SCMEOL enmEol;1512 size_t cchLine;1513 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);1514 1515 RTStrmPutCh(pState->pDiff, chPrefix);1516 if (pchLine && cchLine)1517 {1518 if (!pState->fSpecialChars)1519 RTStrmWrite(pState->pDiff, pchLine, cchLine);1520 else1521 {1522 size_t offVir = 0;1523 const char *pchStart = pchLine;1524 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);1525 while (pchTab)1526 {1527 RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);1528 offVir += pchTab - pchStart;1529 1530 size_t cchTab = pState->cchTab - offVir % pState->cchTab;1531 switch (cchTab)1532 {1533 case 1: RTStrmPutStr(pState->pDiff, "."); break;1534 case 2: RTStrmPutStr(pState->pDiff, ".."); break;1535 case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;1536 case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;1537 case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;1538 default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;1539 }1540 offVir += cchTab;1541 1542 /* next */1543 pchStart = pchTab + 1;1544 pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));1545 }1546 size_t cchLeft = cchLine - (pchStart - pchLine);1547 if (cchLeft)1548 RTStrmWrite(pState->pDiff, pchStart, cchLeft);1549 }1550 }1551 1552 if (!pState->fSpecialChars)1553 RTStrmPutCh(pState->pDiff, '\n');1554 else if (enmEol == SCMEOL_LF)1555 RTStrmPutStr(pState->pDiff, "[LF]\n");1556 else if (enmEol == SCMEOL_CRLF)1557 RTStrmPutStr(pState->pDiff, "[CRLF]\n");1558 else1559 RTStrmPutStr(pState->pDiff, "[NONE]\n");1560 1561 iLine++;1562 }1563 }1564 1565 1566 /**1567 * Reports a difference and propels the streams to the lines following the1568 * resync.1569 *1570 *1571 * @returns New pState->cDiff value (just to return something).1572 * @param pState The diff state. The cDiffs member will be1573 * incremented.1574 * @param cMatches The resync length.1575 * @param iLeft Where the difference starts on the left side.1576 * @param cLeft How long it is on this side. ~(size_t)0 is used1577 * to indicate that it goes all the way to the end.1578 * @param iRight Where the difference starts on the right side.1579 * @param cRight How long it is.1580 */1581 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,1582 size_t iLeft, size_t cLeft,1583 size_t iRight, size_t cRight)1584 {1585 /*1586 * Adjust the input.1587 */1588 if (cLeft == ~(size_t)0)1589 {1590 size_t c = ScmStreamCountLines(pState->pLeft);1591 if (c >= iLeft)1592 cLeft = c - iLeft;1593 else1594 {1595 iLeft = c;1596 cLeft = 0;1597 }1598 }1599 1600 if (cRight == ~(size_t)0)1601 {1602 size_t c = ScmStreamCountLines(pState->pRight);1603 if (c >= iRight)1604 cRight = c - iRight;1605 else1606 {1607 iRight = c;1608 cRight = 0;1609 }1610 }1611 1612 /*1613 * Print header if it's the first difference1614 */1615 if (!pState->cDiffs)1616 RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);1617 1618 /*1619 * Emit the change description.1620 */1621 char ch = cLeft == 01622 ? 'a'1623 : cRight == 01624 ? 'd'1625 : 'c';1626 if (cLeft > 1 && cRight > 1)1627 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);1628 else if (cLeft > 1)1629 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);1630 else if (cRight > 1)1631 RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);1632 else1633 RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);1634 1635 /*1636 * And the lines.1637 */1638 if (cLeft)1639 scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);1640 if (cLeft && cRight)1641 RTStrmPrintf(pState->pDiff, "---\n");1642 if (cRight)1643 scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);1644 1645 /*1646 * Reposition the streams (safely ignores return value).1647 */1648 ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);1649 ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);1650 1651 pState->cDiffs++;1652 return pState->cDiffs;1653 }1654 1655 /**1656 * Helper for scmDiffCompare that takes care of trailing spaces and stuff1657 * like that.1658 */1659 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,1660 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,1661 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)1662 {1663 if (pState->fIgnoreTrailingWhite)1664 {1665 while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))1666 cchLeft--;1667 while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))1668 cchRight--;1669 }1670 1671 if (pState->fIgnoreLeadingWhite)1672 {1673 while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))1674 pchLeft++, cchLeft--;1675 while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))1676 pchRight++, cchRight--;1677 }1678 1679 if ( cchLeft != cchRight1680 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)1681 || memcmp(pchLeft, pchRight, cchLeft))1682 return false;1683 return true;1684 }1685 1686 /**1687 * Compare two lines.1688 *1689 * @returns true if the are equal, false if not.1690 */1691 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,1692 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,1693 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)1694 {1695 if ( cchLeft != cchRight1696 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)1697 || memcmp(pchLeft, pchRight, cchLeft))1698 {1699 if ( pState->fIgnoreTrailingWhite1700 || pState->fIgnoreTrailingWhite)1701 return scmDiffCompareSlow(pState,1702 pchLeft, cchLeft, enmEolLeft,1703 pchRight, cchRight, enmEolRight);1704 return false;1705 }1706 return true;1707 }1708 1709 /**1710 * Compares two sets of lines from the two files.1711 *1712 * @returns true if they matches, false if they don't.1713 * @param pState The diff state.1714 * @param iLeft Where to start in the left stream.1715 * @param iRight Where to start in the right stream.1716 * @param cLines How many lines to compare.1717 */1718 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)1719 {1720 for (size_t iLine = 0; iLine < cLines; iLine++)1721 {1722 SCMEOL enmEolLeft;1723 size_t cchLeft;1724 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);1725 1726 SCMEOL enmEolRight;1727 size_t cchRight;1728 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);1729 1730 if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))1731 return false;1732 }1733 return true;1734 }1735 1736 1737 /**1738 * Resynchronize the two streams and reports the difference.1739 *1740 * Upon return, the streams will be positioned after the block of @a cMatches1741 * lines where it resynchronized them.1742 *1743 * @returns pState->cDiffs (just so we can use it in a return statement).1744 * @param pState The state.1745 * @param cMatches The number of lines that needs to match for the1746 * stream to be considered synchronized again.1747 */1748 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)1749 {1750 size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;1751 size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;1752 Assert(cMatches > 0);1753 1754 /*1755 * Compare each new line from each of the streams will all the preceding1756 * ones, including iStartLeft/Right.1757 */1758 for (size_t iRange = 1; ; iRange++)1759 {1760 /*1761 * Get the next line in the left stream and compare it against all the1762 * preceding lines on the right side.1763 */1764 SCMEOL enmEol;1765 size_t cchLine;1766 const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);1767 if (!pchLine)1768 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);1769 1770 for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)1771 {1772 SCMEOL enmEolRight;1773 size_t cchRight;1774 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,1775 &cchRight, &enmEolRight);1776 if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)1777 && scmDiffCompareLines(pState,1778 iStartLeft + iRange + 1 - cMatches,1779 iStartRight + iRight + 1 - cMatches,1780 cMatches - 1)1781 )1782 return scmDiffReport(pState, cMatches,1783 iStartLeft, iRange + 1 - cMatches,1784 iStartRight, iRight + 1 - cMatches);1785 }1786 1787 /*1788 * Get the next line in the right stream and compare it against all the1789 * lines on the right side.1790 */1791 pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);1792 if (!pchLine)1793 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);1794 1795 for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)1796 {1797 SCMEOL enmEolLeft;1798 size_t cchLeft;1799 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,1800 &cchLeft, &enmEolLeft);1801 if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)1802 && scmDiffCompareLines(pState,1803 iStartLeft + iLeft + 1 - cMatches,1804 iStartRight + iRange + 1 - cMatches,1805 cMatches - 1)1806 )1807 return scmDiffReport(pState, cMatches,1808 iStartLeft, iLeft + 1 - cMatches,1809 iStartRight, iRange + 1 - cMatches);1810 }1811 }1812 }1813 1814 /**1815 * Creates a diff of the changes between the streams @a pLeft and @a pRight.1816 *1817 * This currently only implements the simplest diff format, so no contexts.1818 *1819 * Also, note that we won't detect differences in the final newline of the1820 * streams.1821 *1822 * @returns The number of differences.1823 * @param pszFilename The filename.1824 * @param pLeft The left side stream.1825 * @param pRight The right side stream.1826 * @param fIgnoreEol Whether to ignore end of line markers.1827 * @param fIgnoreLeadingWhite Set if leading white space should be ignored.1828 * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.1829 * @param fSpecialChars Whether to print special chars in a human1830 * readable form or not.1831 * @param cchTab The tab size.1832 * @param pDiff Where to write the diff.1833 */1834 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,1835 bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,1836 size_t cchTab, PRTSTREAM pDiff)1837 {1838 #ifdef RT_STRICT1839 ScmStreamCheckItegrity(pLeft);1840 ScmStreamCheckItegrity(pRight);1841 #endif1842 1843 /*1844 * Set up the diff state.1845 */1846 SCMDIFFSTATE State;1847 State.cDiffs = 0;1848 State.pszFilename = pszFilename;1849 State.pLeft = pLeft;1850 State.pRight = pRight;1851 State.fIgnoreEol = fIgnoreEol;1852 State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;1853 State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;1854 State.fSpecialChars = fSpecialChars;1855 State.cchTab = cchTab;1856 State.pDiff = pDiff;1857 1858 /*1859 * Compare them line by line.1860 */1861 ScmStreamRewindForReading(pLeft);1862 ScmStreamRewindForReading(pRight);1863 const char *pchLeft;1864 const char *pchRight;1865 1866 for (;;)1867 {1868 SCMEOL enmEolLeft;1869 size_t cchLeft;1870 pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);1871 1872 SCMEOL enmEolRight;1873 size_t cchRight;1874 pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);1875 if (!pchLeft || !pchRight)1876 break;1877 1878 if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))1879 scmDiffSynchronize(&State, 3);1880 }1881 1882 /*1883 * Deal with any remaining differences.1884 */1885 if (pchLeft)1886 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);1887 else if (pchRight)1888 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);1889 1890 /*1891 * Report any errors.1892 */1893 if (RT_FAILURE(ScmStreamGetStatus(pLeft)))1894 RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));1895 if (RT_FAILURE(ScmStreamGetStatus(pRight)))1896 RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));1897 1898 return State.cDiffs;1899 }1900 1901 1902 1903 /* -=-=-=-=-=- settings -=-=-=-=-=- */1904 1905 /**1906 * Init a settings structure with settings from @a pSrc.1907 *1908 * @returns IPRT status code1909 * @param pSettings The settings.1910 * @param pSrc The source settings.1911 */1912 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)1913 {1914 *pSettings = *pSrc;1915 1916 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);1917 if (RT_SUCCESS(rc))1918 {1919 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);1920 if (RT_SUCCESS(rc))1921 {1922 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);1923 if (RT_SUCCESS(rc))1924 return VINF_SUCCESS;1925 1926 RTStrFree(pSettings->pszFilterOutFiles);1927 }1928 RTStrFree(pSettings->pszFilterFiles);1929 }1930 1931 pSettings->pszFilterFiles = NULL;1932 pSettings->pszFilterOutFiles = NULL;1933 pSettings->pszFilterOutDirs = NULL;1934 return rc;1935 }1936 1937 /**1938 * Init a settings structure.1939 *1940 * @returns IPRT status code1941 * @param pSettings The settings.1942 */1943 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)1944 {1945 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);1946 }1947 1948 /**1949 * Deletes the settings, i.e. free any dynamically allocated content.1950 *1951 * @param pSettings The settings.1952 */1953 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)1954 {1955 if (pSettings)1956 {1957 Assert(pSettings->cchTab != ~(unsigned)0);1958 pSettings->cchTab = ~(unsigned)0;1959 1960 RTStrFree(pSettings->pszFilterFiles);1961 pSettings->pszFilterFiles = NULL;1962 1963 RTStrFree(pSettings->pszFilterOutFiles);1964 pSettings->pszFilterOutFiles = NULL;1965 1966 RTStrFree(pSettings->pszFilterOutDirs);1967 pSettings->pszFilterOutDirs = NULL;1968 }1969 }1970 1971 1972 /**1973 * Processes a RTGetOpt result.1974 *1975 * @retval VINF_SUCCESS if handled.1976 * @retval VERR_OUT_OF_RANGE if the option value was out of range.1977 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.1978 *1979 * @param pSettings The settings to change.1980 * @param rc The RTGetOpt return value.1981 * @param pValueUnion The RTGetOpt value union.1982 */1983 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)1984 {1985 switch (rc)1986 {1987 case SCMOPT_CONVERT_EOL:1988 pSettings->fConvertEol = true;1989 return VINF_SUCCESS;1990 case SCMOPT_NO_CONVERT_EOL:1991 pSettings->fConvertEol = false;1992 return VINF_SUCCESS;1993 1994 case SCMOPT_CONVERT_TABS:1995 pSettings->fConvertTabs = true;1996 return VINF_SUCCESS;1997 case SCMOPT_NO_CONVERT_TABS:1998 pSettings->fConvertTabs = false;1999 return VINF_SUCCESS;2000 2001 case SCMOPT_FORCE_FINAL_EOL:2002 pSettings->fForceFinalEol = true;2003 return VINF_SUCCESS;2004 case SCMOPT_NO_FORCE_FINAL_EOL:2005 pSettings->fForceFinalEol = false;2006 return VINF_SUCCESS;2007 2008 case SCMOPT_FORCE_TRAILING_LINE:2009 pSettings->fForceTrailingLine = true;2010 return VINF_SUCCESS;2011 case SCMOPT_NO_FORCE_TRAILING_LINE:2012 pSettings->fForceTrailingLine = false;2013 return VINF_SUCCESS;2014 2015 case SCMOPT_STRIP_TRAILING_BLANKS:2016 pSettings->fStripTrailingBlanks = true;2017 return VINF_SUCCESS;2018 case SCMOPT_NO_STRIP_TRAILING_BLANKS:2019 pSettings->fStripTrailingBlanks = false;2020 return VINF_SUCCESS;2021 2022 case SCMOPT_STRIP_TRAILING_LINES:2023 pSettings->fStripTrailingLines = true;2024 return VINF_SUCCESS;2025 case SCMOPT_NO_STRIP_TRAILING_LINES:2026 pSettings->fStripTrailingLines = false;2027 return VINF_SUCCESS;2028 2029 case SCMOPT_ONLY_SVN_DIRS:2030 pSettings->fOnlySvnDirs = true;2031 return VINF_SUCCESS;2032 case SCMOPT_NOT_ONLY_SVN_DIRS:2033 pSettings->fOnlySvnDirs = false;2034 return VINF_SUCCESS;2035 2036 case SCMOPT_ONLY_SVN_FILES:2037 pSettings->fOnlySvnFiles = true;2038 return VINF_SUCCESS;2039 case SCMOPT_NOT_ONLY_SVN_FILES:2040 pSettings->fOnlySvnFiles = false;2041 return VINF_SUCCESS;2042 2043 case SCMOPT_SET_SVN_EOL:2044 pSettings->fSetSvnEol = true;2045 return VINF_SUCCESS;2046 case SCMOPT_DONT_SET_SVN_EOL:2047 pSettings->fSetSvnEol = false;2048 return VINF_SUCCESS;2049 2050 case SCMOPT_SET_SVN_EXECUTABLE:2051 pSettings->fSetSvnExecutable = true;2052 return VINF_SUCCESS;2053 case SCMOPT_DONT_SET_SVN_EXECUTABLE:2054 pSettings->fSetSvnExecutable = false;2055 return VINF_SUCCESS;2056 2057 case SCMOPT_SET_SVN_KEYWORDS:2058 pSettings->fSetSvnKeywords = true;2059 return VINF_SUCCESS;2060 case SCMOPT_DONT_SET_SVN_KEYWORDS:2061 pSettings->fSetSvnKeywords = false;2062 return VINF_SUCCESS;2063 2064 case SCMOPT_TAB_SIZE:2065 if ( pValueUnion->u8 < 12066 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))2067 {2068 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",2069 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);2070 return VERR_OUT_OF_RANGE;2071 }2072 pSettings->cchTab = pValueUnion->u8;2073 return VINF_SUCCESS;2074 2075 case SCMOPT_FILTER_OUT_DIRS:2076 case SCMOPT_FILTER_FILES:2077 case SCMOPT_FILTER_OUT_FILES:2078 {2079 char **ppsz = NULL;2080 switch (rc)2081 {2082 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;2083 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;2084 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;2085 }2086 2087 /*2088 * An empty string zaps the current list.2089 */2090 if (!*pValueUnion->psz)2091 return RTStrATruncate(ppsz, 0);2092 2093 /*2094 * Non-empty strings are appended to the pattern list.2095 *2096 * Strip leading and trailing pattern separators before attempting2097 * to append it. If it's just separators, don't do anything.2098 */2099 const char *pszSrc = pValueUnion->psz;2100 while (*pszSrc == '|')2101 pszSrc++;2102 size_t cchSrc = strlen(pszSrc);2103 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')2104 cchSrc--;2105 if (!cchSrc)2106 return VINF_SUCCESS;2107 2108 return RTStrAAppendExN(ppsz, 2,2109 "|", *ppsz && **ppsz ? 1 : 0,2110 pszSrc, cchSrc);2111 }2112 2113 default:2114 return VERR_GETOPT_UNKNOWN_OPTION;2115 }2116 }2117 2118 /**2119 * Parses an option string.2120 *2121 * @returns IPRT status code.2122 * @param pBase The base settings structure to apply the options2123 * to.2124 * @param pszOptions The options to parse.2125 */2126 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)2127 {2128 int cArgs;2129 char **papszArgs;2130 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);2131 if (RT_SUCCESS(rc))2132 {2133 RTGETOPTUNION ValueUnion;2134 RTGETOPTSTATE GetOptState;2135 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);2136 if (RT_SUCCESS(rc))2137 {2138 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)2139 {2140 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);2141 if (RT_FAILURE(rc))2142 break;2143 }2144 }2145 RTGetOptArgvFree(papszArgs);2146 }2147 2148 return rc;2149 }2150 2151 /**2152 * Parses an unterminated option string.2153 *2154 * @returns IPRT status code.2155 * @param pBase The base settings structure to apply the options2156 * to.2157 * @param pchLine The line.2158 * @param cchLine The line length.2159 */2160 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)2161 {2162 char *pszLine = RTStrDupN(pchLine, cchLine);2163 if (!pszLine)2164 return VERR_NO_MEMORY;2165 int rc = scmSettingsBaseParseString(pBase, pszLine);2166 RTStrFree(pszLine);2167 return rc;2168 }2169 2170 /**2171 * Verifies the options string.2172 *2173 * @returns IPRT status code.2174 * @param pszOptions The options to verify .2175 */2176 static int scmSettingsBaseVerifyString(const char *pszOptions)2177 {2178 SCMSETTINGSBASE Base;2179 int rc = scmSettingsBaseInit(&Base);2180 if (RT_SUCCESS(rc))2181 {2182 rc = scmSettingsBaseParseString(&Base, pszOptions);2183 scmSettingsBaseDelete(&Base);2184 }2185 return rc;2186 }2187 2188 /**2189 * Loads settings found in editor and SCM settings directives within the2190 * document (@a pStream).2191 *2192 * @returns IPRT status code.2193 * @param pBase The settings base to load settings into.2194 * @param pStream The stream to scan for settings directives.2195 */2196 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)2197 {2198 /** @todo Editor and SCM settings directives in documents. */2199 return VINF_SUCCESS;2200 }2201 2202 /**2203 * Creates a new settings file struct, cloning @a pSettings.2204 *2205 * @returns IPRT status code.2206 * @param ppSettings Where to return the new struct.2207 * @param pSettingsBase The settings to inherit from.2208 */2209 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)2210 {2211 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));2212 if (!pSettings)2213 return VERR_NO_MEMORY;2214 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);2215 if (RT_SUCCESS(rc))2216 {2217 pSettings->pDown = NULL;2218 pSettings->pUp = NULL;2219 pSettings->paPairs = NULL;2220 pSettings->cPairs = 0;2221 *ppSettings = pSettings;2222 return VINF_SUCCESS;2223 }2224 RTMemFree(pSettings);2225 return rc;2226 }2227 2228 /**2229 * Destroys a settings structure.2230 *2231 * @param pSettings The settings structure to destroy. NULL is OK.2232 */2233 static void scmSettingsDestroy(PSCMSETTINGS pSettings)2234 {2235 if (pSettings)2236 {2237 scmSettingsBaseDelete(&pSettings->Base);2238 for (size_t i = 0; i < pSettings->cPairs; i++)2239 {2240 RTStrFree(pSettings->paPairs[i].pszPattern);2241 RTStrFree(pSettings->paPairs[i].pszOptions);2242 pSettings->paPairs[i].pszPattern = NULL;2243 pSettings->paPairs[i].pszOptions = NULL;2244 }2245 RTMemFree(pSettings->paPairs);2246 pSettings->paPairs = NULL;2247 RTMemFree(pSettings);2248 }2249 }2250 2251 /**2252 * Adds a pattern/options pair to the settings structure.2253 *2254 * @returns IPRT status code.2255 * @param pSettings The settings.2256 * @param pchLine The line containing the unparsed pair.2257 * @param cchLine The length of the line.2258 */2259 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)2260 {2261 /*2262 * Split the string.2263 */2264 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);2265 if (!pchOptions)2266 return VERR_INVALID_PARAMETER;2267 size_t cchPattern = pchOptions - pchLine;2268 size_t cchOptions = cchLine - cchPattern - 1;2269 pchOptions++;2270 2271 /* strip spaces everywhere */2272 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))2273 cchPattern--;2274 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))2275 cchPattern--, pchLine++;2276 2277 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))2278 cchOptions--;2279 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))2280 cchOptions--, pchOptions++;2281 2282 /* Quietly ignore empty patterns and empty options. */2283 if (!cchOptions || !cchPattern)2284 return VINF_SUCCESS;2285 2286 /*2287 * Add the pair and verify the option string.2288 */2289 uint32_t iPair = pSettings->cPairs;2290 if ((iPair % 32) == 0)2291 {2292 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));2293 if (!pvNew)2294 return VERR_NO_MEMORY;2295 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;2296 }2297 2298 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);2299 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);2300 int rc;2301 if ( pSettings->paPairs[iPair].pszPattern2302 && pSettings->paPairs[iPair].pszOptions)2303 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);2304 else2305 rc = VERR_NO_MEMORY;2306 if (RT_SUCCESS(rc))2307 pSettings->cPairs = iPair + 1;2308 else2309 {2310 RTStrFree(pSettings->paPairs[iPair].pszPattern);2311 RTStrFree(pSettings->paPairs[iPair].pszOptions);2312 }2313 return rc;2314 }2315 2316 /**2317 * Loads in the settings from @a pszFilename.2318 *2319 * @returns IPRT status code.2320 * @param pSettings Where to load the settings file.2321 * @param pszFilename The file to load.2322 */2323 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)2324 {2325 SCMSTREAM Stream;2326 int rc = ScmStreamInitForReading(&Stream, pszFilename);2327 if (RT_FAILURE(rc))2328 {2329 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);2330 return rc;2331 }2332 2333 SCMEOL enmEol;2334 const char *pchLine;2335 size_t cchLine;2336 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)2337 {2338 /* Ignore leading spaces. */2339 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))2340 pchLine++, cchLine--;2341 2342 /* Ignore empty lines and comment lines. */2343 if (cchLine < 1 || *pchLine == '#')2344 continue;2345 2346 /* What kind of line is it? */2347 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);2348 if (pchColon)2349 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);2350 else2351 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);2352 if (RT_FAILURE(rc))2353 {2354 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);2355 break;2356 }2357 }2358 2359 if (RT_SUCCESS(rc))2360 {2361 rc = ScmStreamGetStatus(&Stream);2362 if (RT_FAILURE(rc))2363 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);2364 }2365 2366 ScmStreamDelete(&Stream);2367 return rc;2368 }2369 2370 /**2371 * Parse the specified settings file creating a new settings struct from it.2372 *2373 * @returns IPRT status code2374 * @param ppSettings Where to return the new settings.2375 * @param pszFilename The file to parse.2376 * @param pSettingsBase The base settings we inherit from.2377 */2378 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)2379 {2380 PSCMSETTINGS pSettings;2381 int rc = scmSettingsCreate(&pSettings, pSettingsBase);2382 if (RT_SUCCESS(rc))2383 {2384 rc = scmSettingsLoadFile(pSettings, pszFilename);2385 if (RT_SUCCESS(rc))2386 {2387 *ppSettings = pSettings;2388 return VINF_SUCCESS;2389 }2390 2391 scmSettingsDestroy(pSettings);2392 }2393 *ppSettings = NULL;2394 return rc;2395 }2396 2397 2398 /**2399 * Create an initial settings structure when starting processing a new file or2400 * directory.2401 *2402 * This will look for .scm-settings files from the root and down to the2403 * specified directory, combining them into the returned settings structure.2404 *2405 * @returns IPRT status code.2406 * @param ppSettings Where to return the pointer to the top stack2407 * object.2408 * @param pBaseSettings The base settings we inherit from (globals2409 * typically).2410 * @param pszPath The absolute path to the new directory or file.2411 */2412 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)2413 {2414 *ppSettings = NULL; /* try shut up gcc. */2415 2416 /*2417 * We'll be working with a stack copy of the path.2418 */2419 char szFile[RTPATH_MAX];2420 size_t cchDir = strlen(pszPath);2421 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))2422 return VERR_FILENAME_TOO_LONG;2423 2424 /*2425 * Create the bottom-most settings.2426 */2427 PSCMSETTINGS pSettings;2428 int rc = scmSettingsCreate(&pSettings, pBaseSettings);2429 if (RT_FAILURE(rc))2430 return rc;2431 2432 /*2433 * Enumerate the path components from the root and down. Load any setting2434 * files we find.2435 */2436 size_t cComponents = RTPathCountComponents(pszPath);2437 for (size_t i = 1; i <= cComponents; i++)2438 {2439 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);2440 if (RT_SUCCESS(rc))2441 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);2442 if (RT_FAILURE(rc))2443 break;2444 if (RTFileExists(szFile))2445 {2446 rc = scmSettingsLoadFile(pSettings, szFile);2447 if (RT_FAILURE(rc))2448 break;2449 }2450 }2451 2452 if (RT_SUCCESS(rc))2453 *ppSettings = pSettings;2454 else2455 scmSettingsDestroy(pSettings);2456 return rc;2457 }2458 2459 /**2460 * Pushes a new settings set onto the stack.2461 *2462 * @param ppSettingsStack The pointer to the pointer to the top stack2463 * element. This will be used as input and output.2464 * @param pSettings The settings to push onto the stack.2465 */2466 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)2467 {2468 PSCMSETTINGS pOld = *ppSettingsStack;2469 pSettings->pDown = pOld;2470 pSettings->pUp = NULL;2471 if (pOld)2472 pOld->pUp = pSettings;2473 *ppSettingsStack = pSettings;2474 }2475 2476 /**2477 * Pushes the settings of the specified directory onto the stack.2478 *2479 * We will load any .scm-settings in the directory. A stack entry is added even2480 * if no settings file was found.2481 *2482 * @returns IPRT status code.2483 * @param ppSettingsStack The pointer to the pointer to the top stack2484 * element. This will be used as input and output.2485 * @param pszDir The directory to do this for.2486 */2487 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)2488 {2489 char szFile[RTPATH_MAX];2490 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);2491 if (RT_SUCCESS(rc))2492 {2493 PSCMSETTINGS pSettings;2494 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);2495 if (RT_SUCCESS(rc))2496 {2497 if (RTFileExists(szFile))2498 rc = scmSettingsLoadFile(pSettings, szFile);2499 if (RT_SUCCESS(rc))2500 {2501 scmSettingsStackPush(ppSettingsStack, pSettings);2502 return VINF_SUCCESS;2503 }2504 2505 scmSettingsDestroy(pSettings);2506 }2507 }2508 return rc;2509 }2510 2511 2512 /**2513 * Pops a settings set off the stack.2514 *2515 * @returns The popped setttings.2516 * @param ppSettingsStack The pointer to the pointer to the top stack2517 * element. This will be used as input and output.2518 */2519 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)2520 {2521 PSCMSETTINGS pRet = *ppSettingsStack;2522 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;2523 *ppSettingsStack = pNew;2524 if (pNew)2525 pNew->pUp = NULL;2526 if (pRet)2527 {2528 pRet->pUp = NULL;2529 pRet->pDown = NULL;2530 }2531 return pRet;2532 }2533 2534 /**2535 * Pops and destroys the top entry of the stack.2536 *2537 * @param ppSettingsStack The pointer to the pointer to the top stack2538 * element. This will be used as input and output.2539 */2540 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)2541 {2542 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));2543 }2544 2545 /**2546 * Constructs the base settings for the specified file name.2547 *2548 * @returns IPRT status code.2549 * @param pSettingsStack The top element on the settings stack.2550 * @param pszFilename The file name.2551 * @param pszBasename The base name (pointer within @a pszFilename).2552 * @param cchBasename The length of the base name. (For passing to2553 * RTStrSimplePatternMultiMatch.)2554 * @param pBase Base settings to initialize.2555 */2556 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,2557 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)2558 {2559 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);2560 if (RT_SUCCESS(rc))2561 {2562 /* find the bottom entry in the stack. */2563 PCSCMSETTINGS pCur = pSettingsStack;2564 while (pCur->pDown)2565 pCur = pCur->pDown;2566 2567 /* Work our way up thru the stack and look for matching pairs. */2568 while (pCur)2569 {2570 size_t const cPairs = pCur->cPairs;2571 if (cPairs)2572 {2573 for (size_t i = 0; i < cPairs; i++)2574 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,2575 pszBasename, cchBasename, NULL)2576 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,2577 pszFilename, RTSTR_MAX, NULL))2578 {2579 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);2580 if (RT_FAILURE(rc))2581 break;2582 }2583 if (RT_FAILURE(rc))2584 break;2585 }2586 2587 /* advance */2588 pCur = pCur->pUp;2589 }2590 }2591 if (RT_FAILURE(rc))2592 scmSettingsBaseDelete(pBase);2593 return rc;2594 }2595 2596 2597 /* -=-=-=-=-=- misc -=-=-=-=-=- */2598 2599 2600 /**2601 * Prints a verbose message if the level is high enough.2602 *2603 * @param pState The rewrite state. Optional.2604 * @param iLevel The required verbosity level.2605 * @param pszFormat The message format string. Can be NULL if we2606 * only want to trigger the per file message.2607 * @param ... Format arguments.2608 */2609 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)2610 {2611 if (iLevel <= g_iVerbosity)2612 {2613 if (pState && !pState->fFirst)2614 {2615 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);2616 pState->fFirst = true;2617 }2618 if (pszFormat)2619 {2620 RTPrintf(pState2621 ? "%s: info: "2622 : "%s: info: ",2623 g_szProgName);2624 va_list va;2625 va_start(va, pszFormat);2626 RTPrintfV(pszFormat, va);2627 va_end(va);2628 }2629 }2630 }2631 2632 2633 /* -=-=-=-=-=- subversion -=-=-=-=-=- */2634 2635 #define SCM_WITHOUT_LIBSVN2636 2637 #ifdef SCM_WITHOUT_LIBSVN2638 2639 /**2640 * Callback that is call for each path to search.2641 */2642 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)2643 {2644 char *pszDst = (char *)pvUser1;2645 size_t cchDst = (size_t)pvUser2;2646 if (cchDst > cchPath)2647 {2648 memcpy(pszDst, pchPath, cchPath);2649 pszDst[cchPath] = '\0';2650 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)2651 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");2652 #else2653 int rc = RTPathAppend(pszDst, cchDst, "svn");2654 #endif2655 if ( RT_SUCCESS(rc)2656 && RTFileExists(pszDst))2657 return VINF_SUCCESS;2658 }2659 return VERR_TRY_AGAIN;2660 }2661 2662 2663 /**2664 * Finds the svn binary.2665 *2666 * @param pszPath Where to store it. Worst case, we'll return2667 * "svn" here.2668 * @param cchPath The size of the buffer pointed to by @a pszPath.2669 */2670 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)2671 {2672 /** @todo code page fun... */2673 Assert(cchPath >= sizeof("svn"));2674 #ifdef RT_OS_WINDOWS2675 const char *pszEnvVar = RTEnvGet("Path");2676 #else2677 const char *pszEnvVar = RTEnvGet("PATH");2678 #endif2679 if (pszPath)2680 {2681 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)2682 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);2683 #else2684 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);2685 #endif2686 if (RT_SUCCESS(rc))2687 return;2688 }2689 strcpy(pszPath, "svn");2690 }2691 2692 2693 /**2694 * Construct a dot svn filename for the file being rewritten.2695 *2696 * @returns IPRT status code.2697 * @param pState The rewrite state (for the name).2698 * @param pszDir The directory, including ".svn/".2699 * @param pszSuff The filename suffix.2700 * @param pszDst The output buffer. RTPATH_MAX in size.2701 */2702 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)2703 {2704 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */2705 RTPathStripFilename(pszDst);2706 2707 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);2708 if (RT_SUCCESS(rc))2709 {2710 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));2711 if (RT_SUCCESS(rc))2712 {2713 size_t cchDst = strlen(pszDst);2714 size_t cchSuff = strlen(pszSuff);2715 if (cchDst + cchSuff < RTPATH_MAX)2716 {2717 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);2718 return VINF_SUCCESS;2719 }2720 else2721 rc = VERR_BUFFER_OVERFLOW;2722 }2723 }2724 return rc;2725 }2726 2727 /**2728 * Interprets the specified string as decimal numbers.2729 *2730 * @returns true if parsed successfully, false if not.2731 * @param pch The string (not terminated).2732 * @param cch The string length.2733 * @param pu Where to return the value.2734 */2735 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)2736 {2737 size_t u = 0;2738 while (cch-- > 0)2739 {2740 char ch = *pch++;2741 if (ch < '0' || ch > '9')2742 return false;2743 u *= 10;2744 u += ch - '0';2745 }2746 *pu = u;2747 return true;2748 }2749 2750 #endif /* SCM_WITHOUT_LIBSVN */2751 2752 /**2753 * Checks if the file we're operating on is part of a SVN working copy.2754 *2755 * @returns true if it is, false if it isn't or we cannot tell.2756 * @param pState The rewrite state to work on.2757 */2758 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)2759 {2760 #ifdef SCM_WITHOUT_LIBSVN2761 /*2762 * Hack: check if the .svn/text-base/<file>.svn-base file exists.2763 */2764 char szPath[RTPATH_MAX];2765 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);2766 if (RT_SUCCESS(rc))2767 return RTFileExists(szPath);2768 2769 #else2770 NOREF(pState);2771 #endif2772 return false;2773 }2774 2775 /**2776 * Queries the value of an SVN property.2777 *2778 * This will automatically adjust for scheduled changes.2779 *2780 * @returns IPRT status code.2781 * @retval VERR_INVALID_STATE if not a SVN WC file.2782 * @retval VERR_NOT_FOUND if the property wasn't found.2783 * @param pState The rewrite state to work on.2784 * @param pszName The property name.2785 * @param ppszValue Where to return the property value. Free this2786 * using RTStrFree. Optional.2787 */2788 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)2789 {2790 /*2791 * Look it up in the scheduled changes.2792 */2793 uint32_t i = pState->cSvnPropChanges;2794 while (i-- > 0)2795 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))2796 {2797 const char *pszValue = pState->paSvnPropChanges[i].pszValue;2798 if (!pszValue)2799 return VERR_NOT_FOUND;2800 if (ppszValue)2801 return RTStrDupEx(ppszValue, pszValue);2802 return VINF_SUCCESS;2803 }2804 2805 #ifdef SCM_WITHOUT_LIBSVN2806 /*2807 * Hack: Read the .svn/props/<file>.svn-work file exists.2808 */2809 char szPath[RTPATH_MAX];2810 int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);2811 if (RT_SUCCESS(rc) && !RTFileExists(szPath))2812 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);2813 if (RT_SUCCESS(rc))2814 {2815 SCMSTREAM Stream;2816 rc = ScmStreamInitForReading(&Stream, szPath);2817 if (RT_SUCCESS(rc))2818 {2819 /*2820 * The current format is K len\n<name>\nV len\n<value>\n" ... END.2821 */2822 rc = VERR_NOT_FOUND;2823 size_t const cchName = strlen(pszName);2824 SCMEOL enmEol;2825 size_t cchLine;2826 const char *pchLine;2827 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)2828 {2829 /*2830 * Parse the 'K num' / 'END' line.2831 */2832 if ( cchLine == 32833 && !memcmp(pchLine, "END", 3))2834 break;2835 size_t cchKey;2836 if ( cchLine < 32837 || pchLine[0] != 'K'2838 || pchLine[1] != ' '2839 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)2840 || cchKey == 02841 || cchKey > 4096)2842 {2843 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);2844 rc = VERR_PARSE_ERROR;2845 break;2846 }2847 2848 /*2849 * Match the key and skip to the value line. Don't bother with2850 * names containing EOL markers.2851 */2852 size_t const offKey = ScmStreamTell(&Stream);2853 bool fMatch = cchName == cchKey;2854 if (fMatch)2855 {2856 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);2857 if (!pchLine)2858 break;2859 fMatch = cchLine == cchName2860 && !memcmp(pchLine, pszName, cchName);2861 }2862 2863 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))2864 break;2865 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))2866 break;2867 2868 /*2869 * Read and Parse the 'V num' line.2870 */2871 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);2872 if (!pchLine)2873 break;2874 size_t cchValue;2875 if ( cchLine < 32876 || pchLine[0] != 'V'2877 || pchLine[1] != ' '2878 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)2879 || cchValue > _1M)2880 {2881 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);2882 rc = VERR_PARSE_ERROR;2883 break;2884 }2885 2886 /*2887 * If we have a match, allocate a return buffer and read the2888 * value into it. Otherwise skip this value and continue2889 * searching.2890 */2891 if (fMatch)2892 {2893 if (!ppszValue)2894 rc = VINF_SUCCESS;2895 else2896 {2897 char *pszValue;2898 rc = RTStrAllocEx(&pszValue, cchValue + 1);2899 if (RT_SUCCESS(rc))2900 {2901 rc = ScmStreamRead(&Stream, pszValue, cchValue);2902 if (RT_SUCCESS(rc))2903 *ppszValue = pszValue;2904 else2905 RTStrFree(pszValue);2906 }2907 }2908 break;2909 }2910 2911 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))2912 break;2913 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))2914 break;2915 }2916 2917 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))2918 {2919 rc = ScmStreamGetStatus(&Stream);2920 RTMsgError("%s: stream error %Rrc\n", szPath, rc);2921 }2922 ScmStreamDelete(&Stream);2923 }2924 }2925 2926 if (rc == VERR_FILE_NOT_FOUND)2927 rc = VERR_NOT_FOUND;2928 return rc;2929 2930 #else2931 NOREF(pState);2932 #endif2933 return VERR_NOT_FOUND;2934 }2935 2936 2937 /**2938 * Schedules the setting of a property.2939 *2940 * @returns IPRT status code.2941 * @retval VERR_INVALID_STATE if not a SVN WC file.2942 * @param pState The rewrite state to work on.2943 * @param pszName The name of the property to set.2944 * @param pszValue The value. NULL means deleting it.2945 */2946 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)2947 {2948 /*2949 * Update any existing entry first.2950 */2951 size_t i = pState->cSvnPropChanges;2952 while (i-- > 0)2953 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))2954 {2955 if (!pszValue)2956 {2957 RTStrFree(pState->paSvnPropChanges[i].pszValue);2958 pState->paSvnPropChanges[i].pszValue = NULL;2959 }2960 else2961 {2962 char *pszCopy;2963 int rc = RTStrDupEx(&pszCopy, pszValue);2964 if (RT_FAILURE(rc))2965 return rc;2966 pState->paSvnPropChanges[i].pszValue = pszCopy;2967 }2968 return VINF_SUCCESS;2969 }2970 2971 /*2972 * Insert a new entry.2973 */2974 i = pState->cSvnPropChanges;2975 if ((i % 32) == 0)2976 {2977 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));2978 if (!pvNew)2979 return VERR_NO_MEMORY;2980 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;2981 }2982 2983 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);2984 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;2985 if ( pState->paSvnPropChanges[i].pszName2986 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )2987 pState->cSvnPropChanges = i + 1;2988 else2989 {2990 RTStrFree(pState->paSvnPropChanges[i].pszName);2991 pState->paSvnPropChanges[i].pszName = NULL;2992 RTStrFree(pState->paSvnPropChanges[i].pszValue);2993 pState->paSvnPropChanges[i].pszValue = NULL;2994 return VERR_NO_MEMORY;2995 }2996 return VINF_SUCCESS;2997 }2998 2999 3000 /**3001 * Schedules a property deletion.3002 *3003 * @returns IPRT status code.3004 * @param pState The rewrite state to work on.3005 * @param pszName The name of the property to delete.3006 */3007 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)3008 {3009 return scmSvnSetProperty(pState, pszName, NULL);3010 }3011 3012 3013 /**3014 * Applies any SVN property changes to the work copy of the file.3015 *3016 * @returns IPRT status code.3017 * @param pState The rewrite state which SVN property changes3018 * should be applied.3019 */3020 static int scmSvnDisplayChanges(PSCMRWSTATE pState)3021 {3022 size_t i = pState->cSvnPropChanges;3023 while (i-- > 0)3024 {3025 const char *pszName = pState->paSvnPropChanges[i].pszName;3026 const char *pszValue = pState->paSvnPropChanges[i].pszValue;3027 if (pszValue)3028 ScmVerbose(pState, 0, "svn ps '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);3029 else3030 ScmVerbose(pState, 0, "svn pd '%s' %s\n", pszName, pszValue, pState->pszFilename);3031 }3032 3033 return VINF_SUCCESS;3034 }3035 3036 /**3037 * Applies any SVN property changes to the work copy of the file.3038 *3039 * @returns IPRT status code.3040 * @param pState The rewrite state which SVN property changes3041 * should be applied.3042 */3043 static int scmSvnApplyChanges(PSCMRWSTATE pState)3044 {3045 #ifdef SCM_WITHOUT_LIBSVN3046 /*3047 * This sucks. We gotta find svn(.exe).3048 */3049 static char s_szSvnPath[RTPATH_MAX];3050 if (s_szSvnPath[0] == '\0')3051 scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));3052 3053 /*3054 * Iterate thru the changes and apply them by starting the svn client.3055 */3056 for (size_t i = 0; i <pState->cSvnPropChanges; i++)3057 {3058 const char *apszArgv[6];3059 apszArgv[0] = s_szSvnPath;3060 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";3061 apszArgv[2] = pState->paSvnPropChanges[i].pszName;3062 int iArg = 3;3063 if (pState->paSvnPropChanges[i].pszValue)3064 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;3065 apszArgv[iArg++] = pState->pszFilename;3066 apszArgv[iArg++] = NULL;3067 ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",3068 apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);3069 3070 RTPROCESS pid;3071 int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);3072 if (RT_SUCCESS(rc))3073 {3074 RTPROCSTATUS Status;3075 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);3076 if ( RT_SUCCESS(rc)3077 && ( Status.enmReason != RTPROCEXITREASON_NORMAL3078 || Status.iStatus != 0) )3079 {3080 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",3081 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],3082 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"3083 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"3084 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"3085 : "abducted by alien",3086 Status.iStatus);3087 return VERR_GENERAL_FAILURE;3088 }3089 }3090 if (RT_FAILURE(rc))3091 {3092 RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",3093 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);3094 return rc;3095 }3096 }3097 3098 return VINF_SUCCESS;3099 #else3100 return VERR_NOT_IMPLEMENTED;3101 #endif3102 }3103 3104 3105 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */3106 3107 3108 /**3109 * Strip trailing blanks (space & tab).3110 *3111 * @returns True if modified, false if not.3112 * @param pIn The input stream.3113 * @param pOut The output stream.3114 * @param pSettings The settings.3115 */3116 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3117 {3118 if (!pSettings->fStripTrailingBlanks)3119 return false;3120 3121 bool fModified = false;3122 SCMEOL enmEol;3123 size_t cchLine;3124 const char *pchLine;3125 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3126 {3127 int rc;3128 if ( cchLine == 03129 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )3130 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3131 else3132 {3133 cchLine--;3134 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))3135 cchLine--;3136 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3137 fModified = true;3138 }3139 if (RT_FAILURE(rc))3140 return false;3141 }3142 if (fModified)3143 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");3144 return fModified;3145 }3146 3147 /**3148 * Expand tabs.3149 *3150 * @returns True if modified, false if not.3151 * @param pIn The input stream.3152 * @param pOut The output stream.3153 * @param pSettings The settings.3154 */3155 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3156 {3157 if (!pSettings->fConvertTabs)3158 return false;3159 3160 size_t const cchTab = pSettings->cchTab;3161 bool fModified = false;3162 SCMEOL enmEol;3163 size_t cchLine;3164 const char *pchLine;3165 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3166 {3167 int rc;3168 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);3169 if (!pchTab)3170 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3171 else3172 {3173 size_t offTab = 0;3174 const char *pchChunk = pchLine;3175 for (;;)3176 {3177 size_t cchChunk = pchTab - pchChunk;3178 offTab += cchChunk;3179 ScmStreamWrite(pOut, pchChunk, cchChunk);3180 3181 size_t cchToTab = cchTab - offTab % cchTab;3182 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);3183 offTab += cchToTab;3184 3185 pchChunk = pchTab + 1;3186 size_t cchLeft = cchLine - (pchChunk - pchLine);3187 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);3188 if (!pchTab)3189 {3190 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);3191 break;3192 }3193 }3194 3195 fModified = true;3196 }3197 if (RT_FAILURE(rc))3198 return false;3199 }3200 if (fModified)3201 ScmVerbose(pState, 2, " * Expanded tabs\n");3202 return fModified;3203 }3204 3205 /**3206 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.3207 *3208 * @returns true if modifications were made, false if not.3209 * @param pIn The input stream.3210 * @param pOut The output stream.3211 * @param pSettings The settings.3212 * @param enmDesiredEol The desired end of line indicator type.3213 * @param pszDesiredSvnEol The desired svn:eol-style.3214 */3215 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,3216 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)3217 {3218 if (!pSettings->fConvertEol)3219 return false;3220 3221 bool fModified = false;3222 SCMEOL enmEol;3223 size_t cchLine;3224 const char *pchLine;3225 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3226 {3227 if ( enmEol != enmDesiredEol3228 && enmEol != SCMEOL_NONE)3229 {3230 fModified = true;3231 enmEol = enmDesiredEol;3232 }3233 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3234 if (RT_FAILURE(rc))3235 return false;3236 }3237 if (fModified)3238 ScmVerbose(pState, 2, " * Converted EOL markers\n");3239 3240 /* Check svn:eol-style if appropriate */3241 if ( pSettings->fSetSvnEol3242 && scmSvnIsInWorkingCopy(pState))3243 {3244 char *pszEol;3245 int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);3246 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))3247 || rc == VERR_NOT_FOUND)3248 {3249 if (rc == VERR_NOT_FOUND)3250 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);3251 else3252 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);3253 int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);3254 if (RT_FAILURE(rc2))3255 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */3256 }3257 if (RT_SUCCESS(rc))3258 RTStrFree(pszEol);3259 }3260 3261 /** @todo also check the subversion svn:eol-style state! */3262 return fModified;3263 }3264 3265 /**3266 * Force native end of line indicator.3267 *3268 * @returns true if modifications were made, false if not.3269 * @param pIn The input stream.3270 * @param pOut The output stream.3271 * @param pSettings The settings.3272 */3273 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3274 {3275 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)3276 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");3277 #else3278 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");3279 #endif3280 }3281 3282 /**3283 * Force the stream to use LF as the end of line indicator.3284 *3285 * @returns true if modifications were made, false if not.3286 * @param pIn The input stream.3287 * @param pOut The output stream.3288 * @param pSettings The settings.3289 */3290 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3291 {3292 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");3293 }3294 3295 /**3296 * Force the stream to use CRLF as the end of line indicator.3297 *3298 * @returns true if modifications were made, false if not.3299 * @param pIn The input stream.3300 * @param pOut The output stream.3301 * @param pSettings The settings.3302 */3303 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3304 {3305 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");3306 }3307 3308 /**3309 * Strip trailing blank lines and/or make sure there is exactly one blank line3310 * at the end of the file.3311 *3312 * @returns true if modifications were made, false if not.3313 * @param pIn The input stream.3314 * @param pOut The output stream.3315 * @param pSettings The settings.3316 *3317 * @remarks ASSUMES trailing white space has been removed already.3318 */3319 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3320 {3321 if ( !pSettings->fStripTrailingLines3322 && !pSettings->fForceTrailingLine3323 && !pSettings->fForceFinalEol)3324 return false;3325 3326 size_t const cLines = ScmStreamCountLines(pIn);3327 3328 /* Empty files remains empty. */3329 if (cLines <= 1)3330 return false;3331 3332 /* Figure out if we need to adjust the number of lines or not. */3333 size_t cLinesNew = cLines;3334 3335 if ( pSettings->fStripTrailingLines3336 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))3337 {3338 while ( cLinesNew > 13339 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))3340 cLinesNew--;3341 }3342 3343 if ( pSettings->fForceTrailingLine3344 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))3345 cLinesNew++;3346 3347 bool fFixMissingEol = pSettings->fForceFinalEol3348 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;3349 3350 if ( !fFixMissingEol3351 && cLines == cLinesNew)3352 return false;3353 3354 /* Copy the number of lines we've arrived at. */3355 ScmStreamRewindForReading(pIn);3356 3357 size_t cCopied = RT_MIN(cLinesNew, cLines);3358 ScmStreamCopyLines(pOut, pIn, cCopied);3359 3360 if (cCopied != cLinesNew)3361 {3362 while (cCopied++ < cLinesNew)3363 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));3364 }3365 /* Fix missing EOL if required. */3366 else if (fFixMissingEol)3367 {3368 if (ScmStreamGetEol(pIn) == SCMEOL_LF)3369 ScmStreamWrite(pOut, "\n", 1);3370 else3371 ScmStreamWrite(pOut, "\r\n", 2);3372 }3373 3374 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");3375 return true;3376 }3377 3378 /**3379 * Make sure there is no svn:executable keyword on the current file.3380 *3381 * @returns false - the state carries these kinds of changes.3382 * @param pState The rewriter state.3383 * @param pIn The input stream.3384 * @param pOut The output stream.3385 * @param pSettings The settings.3386 */3387 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3388 {3389 if ( !pSettings->fSetSvnExecutable3390 || !scmSvnIsInWorkingCopy(pState))3391 return false;3392 3393 int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);3394 if (RT_SUCCESS(rc))3395 {3396 ScmVerbose(pState, 2, " * removing svn:executable\n");3397 rc = scmSvnDelProperty(pState, "svn:executable");3398 if (RT_FAILURE(rc))3399 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3400 }3401 return false;3402 }3403 3404 /**3405 * Make sure the Id and Revision keywords are expanded.3406 *3407 * @returns false - the state carries these kinds of changes.3408 * @param pState The rewriter state.3409 * @param pIn The input stream.3410 * @param pOut The output stream.3411 * @param pSettings The settings.3412 */3413 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3414 {3415 if ( !pSettings->fSetSvnKeywords3416 || !scmSvnIsInWorkingCopy(pState))3417 return false;3418 3419 char *pszKeywords;3420 int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);3421 if ( RT_SUCCESS(rc)3422 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */3423 || !strstr(pszKeywords, "Revision")) )3424 {3425 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))3426 rc = RTStrAAppend(&pszKeywords, " Id Revision");3427 else if (!strstr(pszKeywords, "Id"))3428 rc = RTStrAAppend(&pszKeywords, " Id");3429 else3430 rc = RTStrAAppend(&pszKeywords, " Revision");3431 if (RT_SUCCESS(rc))3432 {3433 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);3434 rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);3435 if (RT_FAILURE(rc))3436 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3437 }3438 else3439 RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */3440 RTStrFree(pszKeywords);3441 }3442 else if (rc == VERR_NOT_FOUND)3443 {3444 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");3445 rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");3446 if (RT_FAILURE(rc))3447 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3448 }3449 else if (RT_SUCCESS(rc))3450 RTStrFree(pszKeywords);3451 3452 return false;3453 }3454 3455 /**3456 * Makefile.kup are empty files, enforce this.3457 *3458 * @returns true if modifications were made, false if not.3459 * @param pIn The input stream.3460 * @param pOut The output stream.3461 * @param pSettings The settings.3462 */3463 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3464 {3465 /* These files should be zero bytes. */3466 if (pIn->cb == 0)3467 return false;3468 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");3469 return true;3470 }3471 3472 /**3473 * Rewrite a kBuild makefile.3474 *3475 * @returns true if modifications were made, false if not.3476 * @param pIn The input stream.3477 * @param pOut The output stream.3478 * @param pSettings The settings.3479 *3480 * @todo3481 *3482 * Ideas for Makefile.kmk and Config.kmk:3483 * - sort if1of/ifn1of sets.3484 * - line continuation slashes should only be preceded by one space.3485 */3486 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3487 {3488 return false;3489 }3490 3491 /**3492 * Rewrite a C/C++ source or header file.3493 *3494 * @returns true if modifications were made, false if not.3495 * @param pIn The input stream.3496 * @param pOut The output stream.3497 * @param pSettings The settings.3498 *3499 * @todo3500 *3501 * Ideas for C/C++:3502 * - space after if, while, for, switch3503 * - spaces in for (i=0;i<x;i++)3504 * - complex conditional, bird style.3505 * - remove unnecessary parentheses.3506 * - sort defined RT_OS_*|| and RT_ARCH3507 * - sizeof without parenthesis.3508 * - defined without parenthesis.3509 * - trailing spaces.3510 * - parameter indentation.3511 * - space after comma.3512 * - while (x--); -> multi line + comment.3513 * - else statement;3514 * - space between function and left parenthesis.3515 * - TODO, XXX, @todo cleanup.3516 * - Space before/after '*'.3517 * - ensure new line at end of file.3518 * - Indentation of precompiler statements (#ifdef, #defines).3519 * - space between functions.3520 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.3521 */3522 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3523 {3524 3525 return false;3526 }3527 3528 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */3529 3530 /**3531 * Processes a file.3532 *3533 * @returns IPRT status code.3534 * @param pState The rewriter state.3535 * @param pszFilename The file name.3536 * @param pszBasename The base name (pointer within @a pszFilename).3537 * @param cchBasename The length of the base name. (For passing to3538 * RTStrSimplePatternMultiMatch.)3539 * @param pBaseSettings The base settings to use. It's OK to modify3540 * these.3541 */3542 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,3543 PSCMSETTINGSBASE pBaseSettings)3544 {3545 /*3546 * Do the file level filtering.3547 */3548 if ( pBaseSettings->pszFilterFiles3549 && *pBaseSettings->pszFilterFiles3550 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))3551 {3552 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);3553 return VINF_SUCCESS;3554 }3555 if ( pBaseSettings->pszFilterOutFiles3556 && *pBaseSettings->pszFilterOutFiles3557 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)3558 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )3559 {3560 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);3561 return VINF_SUCCESS;3562 }3563 if ( pBaseSettings->fOnlySvnFiles3564 && !scmSvnIsInWorkingCopy(pState))3565 {3566 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);3567 return VINF_SUCCESS;3568 }3569 3570 /*3571 * Try find a matching rewrite config for this filename.3572 */3573 PCSCMCFGENTRY pCfg = NULL;3574 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)3575 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))3576 {3577 pCfg = &g_aConfigs[iCfg];3578 break;3579 }3580 if (!pCfg)3581 {3582 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);3583 return VINF_SUCCESS;3584 }3585 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);3586 3587 /*3588 * Create an input stream from the file and check that it's text.3589 */3590 SCMSTREAM Stream1;3591 int rc = ScmStreamInitForReading(&Stream1, pszFilename);3592 if (RT_FAILURE(rc))3593 {3594 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);3595 return rc;3596 }3597 if (ScmStreamIsText(&Stream1))3598 {3599 ScmVerbose(pState, 3, NULL);3600 3601 /*3602 * Gather SCM and editor settings from the stream.3603 */3604 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);3605 if (RT_SUCCESS(rc))3606 {3607 ScmStreamRewindForReading(&Stream1);3608 3609 /*3610 * Create two more streams for output and push the text thru all the3611 * rewriters, switching the two streams around when something is3612 * actually rewritten. Stream1 remains unchanged.3613 */3614 SCMSTREAM Stream2;3615 rc = ScmStreamInitForWriting(&Stream2, &Stream1);3616 if (RT_SUCCESS(rc))3617 {3618 SCMSTREAM Stream3;3619 rc = ScmStreamInitForWriting(&Stream3, &Stream1);3620 if (RT_SUCCESS(rc))3621 {3622 bool fModified = false;3623 PSCMSTREAM pIn = &Stream1;3624 PSCMSTREAM pOut = &Stream2;3625 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)3626 {3627 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);3628 if (fRc)3629 {3630 PSCMSTREAM pTmp = pOut;3631 pOut = pIn == &Stream1 ? &Stream3 : pIn;3632 pIn = pTmp;3633 fModified = true;3634 }3635 ScmStreamRewindForReading(pIn);3636 ScmStreamRewindForWriting(pOut);3637 }3638 3639 rc = ScmStreamGetStatus(&Stream1);3640 if (RT_SUCCESS(rc))3641 rc = ScmStreamGetStatus(&Stream2);3642 if (RT_SUCCESS(rc))3643 rc = ScmStreamGetStatus(&Stream3);3644 if (RT_SUCCESS(rc))3645 {3646 /*3647 * If rewritten, write it back to disk.3648 */3649 if (fModified)3650 {3651 if (!g_fDryRun)3652 {3653 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);3654 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);3655 if (RT_FAILURE(rc))3656 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);3657 }3658 else3659 {3660 ScmVerbose(pState, 1, NULL);3661 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,3662 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);3663 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);3664 }3665 }3666 3667 /*3668 * If pending SVN property changes, apply them.3669 */3670 if (pState->cSvnPropChanges && RT_SUCCESS(rc))3671 {3672 if (!g_fDryRun)3673 {3674 rc = scmSvnApplyChanges(pState);3675 if (RT_FAILURE(rc))3676 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);3677 }3678 else3679 scmSvnDisplayChanges(pState);3680 }3681 3682 if (!fModified && !pState->cSvnPropChanges)3683 ScmVerbose(pState, 3, "no change\n", pszFilename);3684 }3685 else3686 RTMsgError("%s: stream error %Rrc\n", pszFilename);3687 ScmStreamDelete(&Stream3);3688 }3689 else3690 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);3691 ScmStreamDelete(&Stream2);3692 }3693 else3694 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);3695 }3696 else3697 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);3698 }3699 else3700 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);3701 ScmStreamDelete(&Stream1);3702 3703 return rc;3704 }3705 3706 /**3707 * Processes a file.3708 *3709 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the3710 * directory recursion method.3711 *3712 * @returns IPRT status code.3713 * @param pszFilename The file name.3714 * @param pszBasename The base name (pointer within @a pszFilename).3715 * @param cchBasename The length of the base name. (For passing to3716 * RTStrSimplePatternMultiMatch.)3717 * @param pSettingsStack The settings stack (pointer to the top element).3718 */3719 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,3720 PSCMSETTINGS pSettingsStack)3721 {3722 SCMSETTINGSBASE Base;3723 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);3724 if (RT_SUCCESS(rc))3725 {3726 SCMRWSTATE State;3727 State.fFirst = false;3728 State.pszFilename = pszFilename;3729 State.cSvnPropChanges = 0;3730 State.paSvnPropChanges = NULL;3731 3732 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);3733 3734 size_t i = State.cSvnPropChanges;3735 while (i-- > 0)3736 {3737 RTStrFree(State.paSvnPropChanges[i].pszName);3738 RTStrFree(State.paSvnPropChanges[i].pszValue);3739 }3740 RTMemFree(State.paSvnPropChanges);3741 3742 scmSettingsBaseDelete(&Base);3743 }3744 return rc;3745 }3746 3747 3748 /**3749 * Tries to correct RTDIRENTRY_UNKNOWN.3750 *3751 * @returns Corrected type.3752 * @param pszPath The path to the object in question.3753 */3754 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)3755 {3756 RTFSOBJINFO Info;3757 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);3758 if (RT_FAILURE(rc))3759 return RTDIRENTRYTYPE_UNKNOWN;3760 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))3761 return RTDIRENTRYTYPE_DIRECTORY;3762 if (RTFS_IS_FILE(Info.Attr.fMode))3763 return RTDIRENTRYTYPE_FILE;3764 return RTDIRENTRYTYPE_UNKNOWN;3765 }3766 3767 /**3768 * Recurse into a sub-directory and process all the files and directories.3769 *3770 * @returns IPRT status code.3771 * @param pszBuf Path buffer containing the directory path on3772 * entry. This ends with a dot. This is passed3773 * along when recursing in order to save stack space3774 * and avoid needless copying.3775 * @param cchDir Length of our path in pszbuf.3776 * @param pEntry Directory entry buffer. This is also passed3777 * along when recursing to save stack space.3778 * @param pSettingsStack The settings stack (pointer to the top element).3779 * @param iRecursion The recursion depth. This is used to restrict3780 * the recursions.3781 */3782 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,3783 PSCMSETTINGS pSettingsStack, unsigned iRecursion)3784 {3785 int rc;3786 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');3787 3788 /*3789 * Make sure we stop somewhere.3790 */3791 if (iRecursion > 128)3792 {3793 RTMsgError("recursion too deep: %d\n", iRecursion);3794 return VINF_SUCCESS; /* ignore */3795 }3796 3797 /*3798 * Check if it's excluded by --only-svn-dir.3799 */3800 if (pSettingsStack->Base.fOnlySvnDirs)3801 {3802 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");3803 if (RT_FAILURE(rc))3804 {3805 RTMsgError("RTPathAppend: %Rrc\n", rc);3806 return rc;3807 }3808 if (!RTDirExists(pszBuf))3809 return VINF_SUCCESS;3810 3811 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));3812 pszBuf[cchDir] = '\0';3813 pszBuf[cchDir - 1] = '.';3814 }3815 3816 /*3817 * Try open and read the directory.3818 */3819 PRTDIR pDir;3820 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);3821 if (RT_FAILURE(rc))3822 {3823 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);3824 return rc;3825 }3826 for (;;)3827 {3828 /* Read the next entry. */3829 rc = RTDirRead(pDir, pEntry, NULL);3830 if (RT_FAILURE(rc))3831 {3832 if (rc == VERR_NO_MORE_FILES)3833 rc = VINF_SUCCESS;3834 else3835 RTMsgError("RTDirRead -> %Rrc\n", rc);3836 break;3837 }3838 3839 /* Skip '.' and '..'. */3840 if ( pEntry->szName[0] == '.'3841 && ( pEntry->cbName == 13842 || ( pEntry->cbName == 23843 && pEntry->szName[1] == '.')))3844 continue;3845 3846 /* Enter it into the buffer so we've got a full name to work3847 with when needed. */3848 if (pEntry->cbName + cchDir >= RTPATH_MAX)3849 {3850 RTMsgError("Skipping too long entry: %s", pEntry->szName);3851 continue;3852 }3853 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);3854 3855 /* Figure the type. */3856 RTDIRENTRYTYPE enmType = pEntry->enmType;3857 if (enmType == RTDIRENTRYTYPE_UNKNOWN)3858 enmType = scmFigureUnknownType(pszBuf);3859 3860 /* Process the file or directory, skip the rest. */3861 if (enmType == RTDIRENTRYTYPE_FILE)3862 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);3863 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)3864 {3865 /* Append the dot for the benefit of the pattern matching. */3866 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)3867 {3868 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);3869 continue;3870 }3871 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));3872 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;3873 3874 if ( !pSettingsStack->Base.pszFilterOutDirs3875 || !*pSettingsStack->Base.pszFilterOutDirs3876 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,3877 pEntry->szName, pEntry->cbName, NULL)3878 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,3879 pszBuf, cchSubDir, NULL)3880 )3881 )3882 {3883 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);3884 if (RT_SUCCESS(rc))3885 {3886 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);3887 scmSettingsStackPopAndDestroy(&pSettingsStack);3888 }3889 }3890 }3891 if (RT_FAILURE(rc))3892 break;3893 }3894 RTDirClose(pDir);3895 return rc;3896 3897 }3898 3899 /**3900 * Process a directory tree.3901 *3902 * @returns IPRT status code.3903 * @param pszDir The directory to start with. This is pointer to3904 * a RTPATH_MAX sized buffer.3905 */3906 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)3907 {3908 /*3909 * Setup the recursion.3910 */3911 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");3912 if (RT_SUCCESS(rc))3913 {3914 RTDIRENTRY Entry;3915 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);3916 }3917 else3918 RTMsgError("RTPathAppend: %Rrc\n", rc);3919 return rc;3920 }3921 3922 3923 /**3924 * Processes a file or directory specified as an command line argument.3925 *3926 * @returns IPRT status code3927 * @param pszSomething What we found in the command line arguments.3928 * @param pSettingsStack The settings stack (pointer to the top element).3929 */3930 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)3931 {3932 char szBuf[RTPATH_MAX];3933 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));3934 if (RT_SUCCESS(rc))3935 {3936 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);3937 3938 PSCMSETTINGS pSettings;3939 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);3940 if (RT_SUCCESS(rc))3941 {3942 scmSettingsStackPush(&pSettingsStack, pSettings);3943 3944 if (RTFileExists(szBuf))3945 {3946 const char *pszBasename = RTPathFilename(szBuf);3947 if (pszBasename)3948 {3949 size_t cchBasename = strlen(pszBasename);3950 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);3951 }3952 else3953 {3954 RTMsgError("RTPathFilename: NULL\n");3955 rc = VERR_IS_A_DIRECTORY;3956 }3957 }3958 else3959 rc = scmProcessDirTree(szBuf, pSettingsStack);3960 3961 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);3962 Assert(pPopped == pSettings);3963 scmSettingsDestroy(pSettings);3964 }3965 else3966 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);3967 }3968 else3969 RTMsgError("RTPathAbs: %Rrc\n", rc);3970 return rc;3971 }3972 3973 int main(int argc, char **argv)3974 {3975 int rc = RTR3InitExe(argc, &argv, 0);3976 if (RT_FAILURE(rc))3977 return 1;3978 3979 /*3980 * Init the settings.3981 */3982 PSCMSETTINGS pSettings;3983 rc = scmSettingsCreate(&pSettings, &g_Defaults);3984 if (RT_FAILURE(rc))3985 {3986 RTMsgError("scmSettingsCreate: %Rrc\n", rc);3987 return 1;3988 }3989 3990 /*3991 * Parse arguments and process input in order (because this is the only3992 * thing that works at the moment).3993 */3994 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =3995 {3996 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },3997 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },3998 { "--file-filter", 'f', RTGETOPT_REQ_STRING },3999 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },4000 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },4001 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },4002 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },4003 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },4004 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },4005 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },4006 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },4007 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },4008 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },4009 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },4010 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },4011 };4012 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));4013 4014 RTGETOPTUNION ValueUnion;4015 RTGETOPTSTATE GetOptState;4016 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);4017 AssertReleaseRCReturn(rc, 1);4018 size_t cProcessed = 0;4019 4020 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)4021 {4022 switch (rc)4023 {4024 case 'd':4025 g_fDryRun = true;4026 break;4027 case 'D':4028 g_fDryRun = false;4029 break;4030 4031 case 'f':4032 g_pszFileFilter = ValueUnion.psz;4033 break;4034 4035 case 'h':4036 RTPrintf("VirtualBox Source Code Massager\n"4037 "\n"4038 "Usage: %s [options] <files & dirs>\n"4039 "\n"4040 "Options:\n", g_szProgName);4041 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)4042 {4043 bool fAdvanceTwo = false;4044 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)4045 {4046 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)4047 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL4048 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL4049 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL4050 );4051 if (fAdvanceTwo)4052 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);4053 else4054 RTPrintf(" %s\n", s_aOpts[i].pszLong);4055 }4056 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)4057 RTPrintf(" %s string\n", s_aOpts[i].pszLong);4058 else4059 RTPrintf(" %s value\n", s_aOpts[i].pszLong);4060 switch (s_aOpts[i].iShort)4061 {4062 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;4063 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;4064 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;4065 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;4066 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;4067 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;4068 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;4069 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;4070 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;4071 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;4072 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;4073 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;4074 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;4075 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;4076 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;4077 }4078 i += fAdvanceTwo;4079 }4080 return 1;4081 4082 case 'q':4083 g_iVerbosity = 0;4084 break;4085 4086 case 'v':4087 g_iVerbosity++;4088 break;4089 4090 case 'V':4091 {4092 /* The following is assuming that svn does it's job here. */4093 static const char s_szRev[] = "$Revision$";4094 const char *psz = RTStrStripL(strchr(s_szRev, ' '));4095 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);4096 return 0;4097 }4098 4099 case SCMOPT_DIFF_IGNORE_EOL:4100 g_fDiffIgnoreEol = true;4101 break;4102 case SCMOPT_DIFF_NO_IGNORE_EOL:4103 g_fDiffIgnoreEol = false;4104 break;4105 4106 case SCMOPT_DIFF_IGNORE_SPACE:4107 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;4108 break;4109 case SCMOPT_DIFF_NO_IGNORE_SPACE:4110 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;4111 break;4112 4113 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:4114 g_fDiffIgnoreLeadingWS = true;4115 break;4116 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:4117 g_fDiffIgnoreLeadingWS = false;4118 break;4119 4120 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:4121 g_fDiffIgnoreTrailingWS = true;4122 break;4123 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:4124 g_fDiffIgnoreTrailingWS = false;4125 break;4126 4127 case SCMOPT_DIFF_SPECIAL_CHARS:4128 g_fDiffSpecialChars = true;4129 break;4130 case SCMOPT_DIFF_NO_SPECIAL_CHARS:4131 g_fDiffSpecialChars = false;4132 break;4133 4134 case VINF_GETOPT_NOT_OPTION:4135 {4136 if (!g_fDryRun)4137 {4138 if (!cProcessed)4139 {4140 RTPrintf("%s: Warning! This program will make changes to your source files and\n"4141 "%s: there is a slight risk that bugs or a full disk may cause\n"4142 "%s: LOSS OF DATA. So, please make sure you have checked in\n"4143 "%s: all your changes already. If you didn't, then don't blame\n"4144 "%s: anyone for not warning you!\n"4145 "%s:\n"4146 "%s: Press any key to continue...\n",4147 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,4148 g_szProgName, g_szProgName);4149 RTStrmGetCh(g_pStdIn);4150 }4151 cProcessed++;4152 }4153 rc = scmProcessSomething(ValueUnion.psz, pSettings);4154 if (RT_FAILURE(rc))4155 return rc;4156 break;4157 }4158 4159 default:4160 {4161 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);4162 if (RT_SUCCESS(rc2))4163 break;4164 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)4165 return 2;4166 return RTGetOptPrintError(rc, &ValueUnion);4167 }4168 }4169 }4170 4171 scmSettingsDestroy(pSettings);4172 return 0;4173 }4174 -
trunk/src/bldprogs/scmstream.h
r40523 r40528 1 1 /* $Id$ */ 2 2 /** @file 3 * IPRT Testcase / Tool - Source Code Massager .3 * IPRT Testcase / Tool - Source Code Massager Stream Code. 4 4 */ 5 5 6 6 /* 7 * Copyright (C) 201 0Oracle Corporation7 * Copyright (C) 2012 Oracle Corporation 8 8 * 9 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 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> 34 #include <iprt/stream.h> 35 #include <iprt/string.h> 18 #ifndef ___scmstream_h___ 19 #define ___scmstream_h___ 36 20 21 #include <iprt/types.h> 37 22 38 /******************************************************************************* 39 * Defined Constants And Macros * 40 *******************************************************************************/ 41 /** The name of the settings files. */ 42 #define SCM_SETTINGS_FILENAME ".scm-settings" 43 44 45 /******************************************************************************* 46 * Structures and Typedefs * 47 *******************************************************************************/ 48 /** Pointer to const massager settings. */ 49 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; 23 RT_C_DECLS_BEGIN 50 24 51 25 /** End of line marker type. */ … … 115 89 116 90 117 /** 118 * SVN property. 119 */ 120 typedef struct SCMSVNPROP 121 { 122 /** The property. */ 123 char *pszName; 124 /** The value. 125 * When used to record updates, this can be set to NULL to trigger the 126 * deletion of the property. */ 127 char *pszValue; 128 } SCMSVNPROP; 129 /** Pointer to a SVN property. */ 130 typedef SCMSVNPROP *PSCMSVNPROP; 131 /** Pointer to a const SVN property. */ 132 typedef SCMSVNPROP const *PCSCMSVNPROP; 91 int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename); 92 int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream); 93 void ScmStreamDelete(PSCMSTREAM pStream); 94 int ScmStreamGetStatus(PCSCMSTREAM pStream); 95 void ScmStreamRewindForReading(PSCMSTREAM pStream); 96 void ScmStreamRewindForWriting(PSCMSTREAM pStream); 97 bool ScmStreamIsText(PSCMSTREAM pStream); 98 int ScmStreamCheckItegrity(PSCMSTREAM pStream); 99 int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...); 100 size_t ScmStreamTell(PSCMSTREAM pStream); 101 size_t ScmStreamTellLine(PSCMSTREAM pStream); 102 size_t ScmStreamSize(PSCMSTREAM pStream); 103 size_t ScmStreamCountLines(PSCMSTREAM pStream); 104 int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute); 105 int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative); 106 int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine); 107 const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol); 108 const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol); 109 int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead); 110 bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine); 111 SCMEOL ScmStreamGetEol(PSCMSTREAM pStream); 112 SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine); 113 int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol); 114 int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf); 115 int ScmStreamPutCh(PSCMSTREAM pStream, char ch); 116 int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines); 133 117 118 RT_C_DECLS_END 134 119 135 /**136 * Rewriter state.137 */138 typedef struct SCMRWSTATE139 {140 /** The filename. */141 const char *pszFilename;142 /** Set after the printing the first verbose message about a file under143 * rewrite. */144 bool fFirst;145 /** The number of SVN property changes. */146 size_t cSvnPropChanges;147 /** Pointer to an array of SVN property changes. */148 PSCMSVNPROP paSvnPropChanges;149 } SCMRWSTATE;150 /** Pointer to the rewriter state. */151 typedef SCMRWSTATE *PSCMRWSTATE;152 153 /**154 * A rewriter.155 *156 * This works like a stream editor, reading @a pIn, modifying it and writing it157 * to @a pOut.158 *159 * @returns true if any changes were made, false if not.160 * @param pIn The input stream.161 * @param pOut The output stream.162 * @param pSettings The settings.163 */164 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);165 166 167 /**168 * Configuration entry.169 */170 typedef struct SCMCFGENTRY171 {172 /** Number of rewriters. */173 size_t cRewriters;174 /** Pointer to an array of rewriters. */175 PFNSCMREWRITER const *papfnRewriter;176 /** File pattern (simple). */177 const char *pszFilePattern;178 } SCMCFGENTRY;179 typedef SCMCFGENTRY *PSCMCFGENTRY;180 typedef SCMCFGENTRY const *PCSCMCFGENTRY;181 182 183 /**184 * Diff state.185 */186 typedef struct SCMDIFFSTATE187 {188 size_t cDiffs;189 const char *pszFilename;190 191 PSCMSTREAM pLeft;192 PSCMSTREAM pRight;193 194 /** Whether to ignore end of line markers when diffing. */195 bool fIgnoreEol;196 /** Whether to ignore trailing whitespace. */197 bool fIgnoreTrailingWhite;198 /** Whether to ignore leading whitespace. */199 bool fIgnoreLeadingWhite;200 /** Whether to print special characters in human readable form or not. */201 bool fSpecialChars;202 /** The tab size. */203 size_t cchTab;204 /** Where to push the diff. */205 PRTSTREAM pDiff;206 } SCMDIFFSTATE;207 /** Pointer to a diff state. */208 typedef SCMDIFFSTATE *PSCMDIFFSTATE;209 210 /**211 * Source Code Massager Settings.212 */213 typedef struct SCMSETTINGSBASE214 {215 bool fConvertEol;216 bool fConvertTabs;217 bool fForceFinalEol;218 bool fForceTrailingLine;219 bool fStripTrailingBlanks;220 bool fStripTrailingLines;221 /** Only process files that are part of a SVN working copy. */222 bool fOnlySvnFiles;223 /** Only recurse into directories containing an .svn dir. */224 bool fOnlySvnDirs;225 /** Set svn:eol-style if missing or incorrect. */226 bool fSetSvnEol;227 /** Set svn:executable according to type (unusually this means deleting it). */228 bool fSetSvnExecutable;229 /** Set svn:keyword if completely or partially missing. */230 bool fSetSvnKeywords;231 /** */232 unsigned cchTab;233 /** Only consider files matching these patterns. This is only applied to the234 * base names. */235 char *pszFilterFiles;236 /** Filter out files matching the following patterns. This is applied to base237 * names as well as the absolute paths. */238 char *pszFilterOutFiles;239 /** Filter out directories matching the following patterns. This is applied240 * to base names as well as the absolute paths. All absolute paths ends with a241 * slash and dot ("/."). */242 char *pszFilterOutDirs;243 } SCMSETTINGSBASE;244 /** Pointer to massager settings. */245 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;246 247 /**248 * Option identifiers.249 *250 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &251 * clear. So, the option setting a flag (boolean) will have an even252 * number and the one clearing it will have an odd number.253 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.254 */255 typedef enum SCMOPT256 {257 SCMOPT_CONVERT_EOL = 10000,258 SCMOPT_NO_CONVERT_EOL,259 SCMOPT_CONVERT_TABS,260 SCMOPT_NO_CONVERT_TABS,261 SCMOPT_FORCE_FINAL_EOL,262 SCMOPT_NO_FORCE_FINAL_EOL,263 SCMOPT_FORCE_TRAILING_LINE,264 SCMOPT_NO_FORCE_TRAILING_LINE,265 SCMOPT_STRIP_TRAILING_BLANKS,266 SCMOPT_NO_STRIP_TRAILING_BLANKS,267 SCMOPT_STRIP_TRAILING_LINES,268 SCMOPT_NO_STRIP_TRAILING_LINES,269 SCMOPT_ONLY_SVN_DIRS,270 SCMOPT_NOT_ONLY_SVN_DIRS,271 SCMOPT_ONLY_SVN_FILES,272 SCMOPT_NOT_ONLY_SVN_FILES,273 SCMOPT_SET_SVN_EOL,274 SCMOPT_DONT_SET_SVN_EOL,275 SCMOPT_SET_SVN_EXECUTABLE,276 SCMOPT_DONT_SET_SVN_EXECUTABLE,277 SCMOPT_SET_SVN_KEYWORDS,278 SCMOPT_DONT_SET_SVN_KEYWORDS,279 SCMOPT_TAB_SIZE,280 SCMOPT_FILTER_OUT_DIRS,281 SCMOPT_FILTER_FILES,282 SCMOPT_FILTER_OUT_FILES,283 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,284 //285 SCMOPT_DIFF_IGNORE_EOL,286 SCMOPT_DIFF_NO_IGNORE_EOL,287 SCMOPT_DIFF_IGNORE_SPACE,288 SCMOPT_DIFF_NO_IGNORE_SPACE,289 SCMOPT_DIFF_IGNORE_LEADING_SPACE,290 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,291 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,292 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,293 SCMOPT_DIFF_SPECIAL_CHARS,294 SCMOPT_DIFF_NO_SPECIAL_CHARS,295 SCMOPT_END296 } SCMOPT;297 298 299 /**300 * File/dir pattern + options.301 */302 typedef struct SCMPATRNOPTPAIR303 {304 char *pszPattern;305 char *pszOptions;306 } SCMPATRNOPTPAIR;307 /** Pointer to a pattern + option pair. */308 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;309 310 311 /** Pointer to a settings set. */312 typedef struct SCMSETTINGS *PSCMSETTINGS;313 /**314 * Settings set.315 *316 * This structure is constructed from the command line arguments or any317 * .scm-settings file found in a directory we recurse into. When recursing in318 * and out of a directory, we push and pop a settings set for it.319 *320 * The .scm-settings file has two kinds of setttings, first there are the321 * unqualified base settings and then there are the settings which applies to a322 * set of files or directories. The former are lines with command line options.323 * For the latter, the options are preceded by a string pattern and a colon.324 * The pattern specifies which files (and/or directories) the options applies325 * to.326 *327 * We parse the base options into the Base member and put the others into the328 * paPairs array.329 */330 typedef struct SCMSETTINGS331 {332 /** Pointer to the setting file below us in the stack. */333 PSCMSETTINGS pDown;334 /** Pointer to the setting file above us in the stack. */335 PSCMSETTINGS pUp;336 /** File/dir patterns and their options. */337 PSCMPATRNOPTPAIR paPairs;338 /** The number of entires in paPairs. */339 uint32_t cPairs;340 /** The base settings that was read out of the file. */341 SCMSETTINGSBASE Base;342 } SCMSETTINGS;343 /** Pointer to a const settings set. */344 typedef SCMSETTINGS const *PCSCMSETTINGS;345 346 347 /*******************************************************************************348 * Internal Functions *349 *******************************************************************************/350 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);351 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);352 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);353 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);354 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);355 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);356 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);358 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);359 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);360 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);361 362 363 /*******************************************************************************364 * Global Variables *365 *******************************************************************************/366 static const char g_szProgName[] = "scm";367 static const char *g_pszChangedSuff = "";368 static const char g_szTabSpaces[16+1] = " ";369 static bool g_fDryRun = true;370 static bool g_fDiffSpecialChars = true;371 static bool g_fDiffIgnoreEol = false;372 static bool g_fDiffIgnoreLeadingWS = false;373 static bool g_fDiffIgnoreTrailingWS = false;374 static int g_iVerbosity = 2;//99; //0;375 376 /** The global settings. */377 static SCMSETTINGSBASE const g_Defaults =378 {379 /* .fConvertEol = */ true,380 /* .fConvertTabs = */ true,381 /* .fForceFinalEol = */ true,382 /* .fForceTrailingLine = */ false,383 /* .fStripTrailingBlanks = */ true,384 /* .fStripTrailingLines = */ true,385 /* .fOnlySvnFiles = */ false,386 /* .fOnlySvnDirs = */ false,387 /* .fSetSvnEol = */ false,388 /* .fSetSvnExecutable = */ false,389 /* .fSetSvnKeywords = */ false,390 /* .cchTab = */ 8,391 /* .pszFilterFiles = */ (char *)"",392 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",393 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",394 };395 396 /** Option definitions for the base settings. */397 static RTGETOPTDEF g_aScmOpts[] =398 {399 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },400 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },401 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },402 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },403 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },404 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },405 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },406 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },407 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },408 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },409 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },410 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },411 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },412 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },413 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },414 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },415 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },416 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },417 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },418 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },419 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },420 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },421 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },422 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },423 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },424 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },425 };426 427 /** Consider files matching the following patterns (base names only). */428 static const char *g_pszFileFilter = NULL;429 430 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =431 {432 rewrite_SvnNoExecutable,433 rewrite_Makefile_kup434 };435 436 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =437 {438 rewrite_ForceNativeEol,439 rewrite_StripTrailingBlanks,440 rewrite_AdjustTrailingLines,441 rewrite_SvnNoExecutable,442 rewrite_SvnKeywords,443 rewrite_Makefile_kmk444 };445 446 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =447 {448 rewrite_ForceNativeEol,449 rewrite_ExpandTabs,450 rewrite_StripTrailingBlanks,451 rewrite_AdjustTrailingLines,452 rewrite_SvnNoExecutable,453 rewrite_SvnKeywords,454 rewrite_C_and_CPP455 };456 457 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =458 {459 rewrite_ForceNativeEol,460 rewrite_ExpandTabs,461 rewrite_StripTrailingBlanks,462 rewrite_AdjustTrailingLines,463 rewrite_SvnNoExecutable,464 rewrite_C_and_CPP465 };466 467 static PFNSCMREWRITER const g_aRewritersFor_RC[] =468 {469 rewrite_ForceNativeEol,470 rewrite_ExpandTabs,471 rewrite_StripTrailingBlanks,472 rewrite_AdjustTrailingLines,473 rewrite_SvnNoExecutable,474 rewrite_SvnKeywords475 };476 477 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =478 {479 rewrite_ForceLF,480 rewrite_ExpandTabs,481 rewrite_StripTrailingBlanks482 };483 484 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =485 {486 rewrite_ForceCRLF,487 rewrite_ExpandTabs,488 rewrite_StripTrailingBlanks489 };490 491 static SCMCFGENTRY const g_aConfigs[] =492 {493 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },494 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },495 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },496 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },497 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },498 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },499 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },500 };501 502 503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */504 505 506 /**507 * Initializes the stream structure.508 *509 * @param pStream The stream structure.510 * @param fWriteOrRead The value of the fWriteOrRead stream member.511 */512 static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)513 {514 pStream->pch = NULL;515 pStream->off = 0;516 pStream->cb = 0;517 pStream->cbAllocated = 0;518 519 pStream->paLines = NULL;520 pStream->iLine = 0;521 pStream->cLines = 0;522 pStream->cLinesAllocated = 0;523 524 pStream->fWriteOrRead = fWriteOrRead;525 pStream->fFileMemory = false;526 pStream->fFullyLineated = false;527 528 pStream->rc = VINF_SUCCESS;529 }530 531 /**532 * Initialize an input stream.533 *534 * @returns IPRT status code.535 * @param pStream The stream to initialize.536 * @param pszFilename The file to take the stream content from.537 */538 int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)539 {540 scmStreamInitInternal(pStream, false /*fWriteOrRead*/);541 542 void *pvFile;543 size_t cbFile;544 int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);545 if (RT_SUCCESS(rc))546 {547 pStream->pch = (char *)pvFile;548 pStream->cb = cbFile;549 pStream->cbAllocated = cbFile;550 pStream->fFileMemory = true;551 }552 return rc;553 }554 555 /**556 * Initialize an output stream.557 *558 * @returns IPRT status code559 * @param pStream The stream to initialize.560 * @param pRelatedStream Pointer to a related stream. NULL is fine.561 */562 int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)563 {564 scmStreamInitInternal(pStream, true /*fWriteOrRead*/);565 566 /* allocate stuff */567 size_t cbEstimate = pRelatedStream568 ? pRelatedStream->cb + pRelatedStream->cb / 10569 : _64K;570 cbEstimate = RT_ALIGN(cbEstimate, _4K);571 pStream->pch = (char *)RTMemAlloc(cbEstimate);572 if (pStream->pch)573 {574 size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated575 ? pRelatedStream->cLines + pRelatedStream->cLines / 10576 : cbEstimate / 24;577 cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);578 pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));579 if (pStream->paLines)580 {581 pStream->paLines[0].off = 0;582 pStream->paLines[0].cch = 0;583 pStream->paLines[0].enmEol = SCMEOL_NONE;584 pStream->cbAllocated = cbEstimate;585 pStream->cLinesAllocated = cLinesEstimate;586 return VINF_SUCCESS;587 }588 589 RTMemFree(pStream->pch);590 pStream->pch = NULL;591 }592 return pStream->rc = VERR_NO_MEMORY;593 }594 595 /**596 * Frees the resources associated with the stream.597 *598 * Nothing is happens to whatever the stream was initialized from or dumped to.599 *600 * @param pStream The stream to delete.601 */602 void ScmStreamDelete(PSCMSTREAM pStream)603 {604 if (pStream->pch)605 {606 if (pStream->fFileMemory)607 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);608 else609 RTMemFree(pStream->pch);610 pStream->pch = NULL;611 }612 pStream->cbAllocated = 0;613 614 if (pStream->paLines)615 {616 RTMemFree(pStream->paLines);617 pStream->paLines = NULL;618 }619 pStream->cLinesAllocated = 0;620 }621 622 /**623 * Get the stream status code.624 *625 * @returns IPRT status code.626 * @param pStream The stream.627 */628 int ScmStreamGetStatus(PCSCMSTREAM pStream)629 {630 return pStream->rc;631 }632 633 /**634 * Grows the buffer of a write stream.635 *636 * @returns IPRT status code.637 * @param pStream The stream. Must be in write mode.638 * @param cbAppending The minimum number of bytes to grow the buffer639 * with.640 */641 static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)642 {643 size_t cbAllocated = pStream->cbAllocated;644 cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);645 cbAllocated = RT_ALIGN(cbAllocated, 0x1000);646 void *pvNew;647 if (!pStream->fFileMemory)648 {649 pvNew = RTMemRealloc(pStream->pch, cbAllocated);650 if (!pvNew)651 return pStream->rc = VERR_NO_MEMORY;652 }653 else654 {655 pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);656 if (!pvNew)657 return pStream->rc = VERR_NO_MEMORY;658 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);659 pStream->fFileMemory = false;660 }661 pStream->pch = (char *)pvNew;662 pStream->cbAllocated = cbAllocated;663 664 return VINF_SUCCESS;665 }666 667 /**668 * Grows the line array of a stream.669 *670 * @returns IPRT status code.671 * @param pStream The stream.672 * @param iMinLine Minimum line number.673 */674 static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)675 {676 size_t cLinesAllocated = pStream->cLinesAllocated;677 cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);678 cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);679 void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));680 if (!pvNew)681 return pStream->rc = VERR_NO_MEMORY;682 683 pStream->paLines = (PSCMSTREAMLINE)pvNew;684 pStream->cLinesAllocated = cLinesAllocated;685 return VINF_SUCCESS;686 }687 688 /**689 * Rewinds the stream and sets the mode to read.690 *691 * @param pStream The stream.692 */693 void ScmStreamRewindForReading(PSCMSTREAM pStream)694 {695 pStream->off = 0;696 pStream->iLine = 0;697 pStream->fWriteOrRead = false;698 pStream->rc = VINF_SUCCESS;699 }700 701 /**702 * Rewinds the stream and sets the mode to write.703 *704 * @param pStream The stream.705 */706 void ScmStreamRewindForWriting(PSCMSTREAM pStream)707 {708 pStream->off = 0;709 pStream->iLine = 0;710 pStream->cLines = 0;711 pStream->fWriteOrRead = true;712 pStream->fFullyLineated = true;713 pStream->rc = VINF_SUCCESS;714 }715 716 /**717 * Checks if it's a text stream.718 *719 * Not 100% proof.720 *721 * @returns true if it probably is a text file, false if not.722 * @param pStream The stream. Write or read, doesn't matter.723 */724 bool ScmStreamIsText(PSCMSTREAM pStream)725 {726 if (RTStrEnd(pStream->pch, pStream->cb))727 return false;728 if (!pStream->cb)729 return false;730 return true;731 }732 733 /**734 * Performs an integrity check of the stream.735 *736 * @returns IPRT status code.737 * @param pStream The stream.738 */739 int ScmStreamCheckItegrity(PSCMSTREAM pStream)740 {741 /*742 * Perform sanity checks.743 */744 size_t const cbFile = pStream->cb;745 for (size_t iLine = 0; iLine < pStream->cLines; iLine++)746 {747 size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;748 AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);749 switch (pStream->paLines[iLine].enmEol)750 {751 case SCMEOL_LF:752 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);753 break;754 case SCMEOL_CRLF:755 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);756 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);757 break;758 case SCMEOL_NONE:759 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);760 break;761 default:762 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);763 }764 }765 return VINF_SUCCESS;766 }767 768 /**769 * Writes the stream to a file.770 *771 * @returns IPRT status code772 * @param pStream The stream.773 * @param pszFilenameFmt The filename format string.774 * @param ... Format arguments.775 */776 int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)777 {778 int rc;779 780 #ifdef RT_STRICT781 /*782 * Check that what we're going to write makes sense first.783 */784 rc = ScmStreamCheckItegrity(pStream);785 if (RT_FAILURE(rc))786 return rc;787 120 #endif 788 121 789 /*790 * Do the actual writing.791 */792 RTFILE hFile;793 va_list va;794 va_start(va, pszFilenameFmt);795 rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);796 if (RT_SUCCESS(rc))797 {798 rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);799 RTFileClose(hFile);800 }801 return rc;802 }803 804 /**805 * Worker for ScmStreamGetLine that builds the line number index while parsing806 * the stream.807 *808 * @returns Same as SCMStreamGetLine.809 * @param pStream The stream. Must be in read mode.810 * @param pcchLine Where to return the line length.811 * @param penmEol Where to return the kind of end of line marker.812 */813 static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)814 {815 AssertReturn(!pStream->fWriteOrRead, NULL);816 if (RT_FAILURE(pStream->rc))817 return NULL;818 819 size_t off = pStream->off;820 size_t cb = pStream->cb;821 if (RT_UNLIKELY(off >= cb))822 {823 pStream->fFullyLineated = true;824 return NULL;825 }826 827 size_t iLine = pStream->iLine;828 if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))829 {830 int rc = scmStreamGrowLines(pStream, iLine);831 if (RT_FAILURE(rc))832 return NULL;833 }834 pStream->paLines[iLine].off = off;835 836 cb -= off;837 const char *pchRet = &pStream->pch[off];838 const char *pch = (const char *)memchr(pchRet, '\n', cb);839 if (RT_LIKELY(pch))840 {841 cb = pch - pchRet;842 pStream->off = off + cb + 1;843 if ( cb < 1844 || pch[-1] != '\r')845 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;846 else847 {848 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;849 cb--;850 }851 }852 else853 {854 pStream->off = off + cb;855 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;856 }857 *pcchLine = cb;858 pStream->paLines[iLine].cch = cb;859 pStream->cLines = pStream->iLine = ++iLine;860 861 return pchRet;862 }863 864 /**865 * Internal worker that delineates a stream.866 *867 * @returns IPRT status code.868 * @param pStream The stream. Caller must check that it is in869 * read mode.870 */871 static int scmStreamLineate(PSCMSTREAM pStream)872 {873 /* Save the stream position. */874 size_t const offSaved = pStream->off;875 size_t const iLineSaved = pStream->iLine;876 877 /* Get each line. */878 size_t cchLine;879 SCMEOL enmEol;880 while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))881 /* nothing */;882 Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);883 884 /* Restore the position */885 pStream->off = offSaved;886 pStream->iLine = iLineSaved;887 888 return pStream->rc;889 }890 891 /**892 * Get the current stream position as an byte offset.893 *894 * @returns The current byte offset895 * @param pStream The stream.896 */897 size_t ScmStreamTell(PSCMSTREAM pStream)898 {899 return pStream->off;900 }901 902 /**903 * Get the current stream position as a line number.904 *905 * @returns The current line (0-based).906 * @param pStream The stream.907 */908 size_t ScmStreamTellLine(PSCMSTREAM pStream)909 {910 return pStream->iLine;911 }912 913 /**914 * Get the current stream size in bytes.915 *916 * @returns Count of bytes.917 * @param pStream The stream.918 */919 size_t ScmStreamSize(PSCMSTREAM pStream)920 {921 return pStream->cb;922 }923 924 /**925 * Gets the number of lines in the stream.926 *927 * @returns The number of lines.928 * @param pStream The stream.929 */930 size_t ScmStreamCountLines(PSCMSTREAM pStream)931 {932 if (!pStream->fFullyLineated)933 scmStreamLineate(pStream);934 return pStream->cLines;935 }936 937 /**938 * Seeks to a given byte offset in the stream.939 *940 * @returns IPRT status code.941 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.942 * This is a temporary restriction.943 *944 * @param pStream The stream. Must be in read mode.945 * @param offAbsolute The offset to seek to. If this is beyond the946 * end of the stream, the position is set to the947 * end.948 */949 int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)950 {951 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);952 if (RT_FAILURE(pStream->rc))953 return pStream->rc;954 955 /* Must be fully delineated. (lazy bird) */956 if (RT_UNLIKELY(!pStream->fFullyLineated))957 {958 int rc = scmStreamLineate(pStream);959 if (RT_FAILURE(rc))960 return rc;961 }962 963 /* Ok, do the job. */964 if (offAbsolute < pStream->cb)965 {966 /** @todo Should do a binary search here, but I'm too darn lazy tonight. */967 pStream->off = ~(size_t)0;968 for (size_t i = 0; i < pStream->cLines; i++)969 {970 if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)971 {972 pStream->off = offAbsolute;973 pStream->iLine = i;974 if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)975 return pStream->rc = VERR_SEEK;976 break;977 }978 }979 AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);980 }981 else982 {983 pStream->off = pStream->cb;984 pStream->iLine = pStream->cLines;985 }986 return VINF_SUCCESS;987 }988 989 990 /**991 * Seeks a number of bytes relative to the current stream position.992 *993 * @returns IPRT status code.994 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.995 * This is a temporary restriction.996 *997 * @param pStream The stream. Must be in read mode.998 * @param offRelative The offset to seek to. A negative offset999 * rewinds and positive one fast forwards the1000 * stream. Will quietly stop at the beginning and1001 * end of the stream.1002 */1003 int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)1004 {1005 size_t offAbsolute;1006 if (offRelative >= 0)1007 offAbsolute = pStream->off + offRelative;1008 else if ((size_t)-offRelative <= pStream->off)1009 offAbsolute = pStream->off + offRelative;1010 else1011 offAbsolute = 0;1012 return ScmStreamSeekAbsolute(pStream, offAbsolute);1013 }1014 1015 /**1016 * Seeks to a given line in the stream.1017 *1018 * @returns IPRT status code.1019 *1020 * @param pStream The stream. Must be in read mode.1021 * @param iLine The line to seek to. If this is beyond the end1022 * of the stream, the position is set to the end.1023 */1024 int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)1025 {1026 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);1027 if (RT_FAILURE(pStream->rc))1028 return pStream->rc;1029 1030 /* Must be fully delineated. (lazy bird) */1031 if (RT_UNLIKELY(!pStream->fFullyLineated))1032 {1033 int rc = scmStreamLineate(pStream);1034 if (RT_FAILURE(rc))1035 return rc;1036 }1037 1038 /* Ok, do the job. */1039 if (iLine < pStream->cLines)1040 {1041 pStream->off = pStream->paLines[iLine].off;1042 pStream->iLine = iLine;1043 }1044 else1045 {1046 pStream->off = pStream->cb;1047 pStream->iLine = pStream->cLines;1048 }1049 return VINF_SUCCESS;1050 }1051 1052 /**1053 * Get a numbered line from the stream (changes the position).1054 *1055 * A line is always delimited by a LF character or the end of the stream. The1056 * delimiter is not included in returned line length, but instead returned via1057 * the @a penmEol indicator.1058 *1059 * @returns Pointer to the first character in the line, not NULL terminated.1060 * NULL if the end of the stream has been reached or some problem1061 * occurred.1062 *1063 * @param pStream The stream. Must be in read mode.1064 * @param iLine The line to get (0-based).1065 * @param pcchLine The length.1066 * @param penmEol Where to return the end of line type indicator.1067 */1068 static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)1069 {1070 AssertReturn(!pStream->fWriteOrRead, NULL);1071 if (RT_FAILURE(pStream->rc))1072 return NULL;1073 1074 /* Make sure it's fully delineated so we can use the index. */1075 if (RT_UNLIKELY(!pStream->fFullyLineated))1076 {1077 int rc = scmStreamLineate(pStream);1078 if (RT_FAILURE(rc))1079 return NULL;1080 }1081 1082 /* End of stream? */1083 if (RT_UNLIKELY(iLine >= pStream->cLines))1084 {1085 pStream->off = pStream->cb;1086 pStream->iLine = pStream->cLines;1087 return NULL;1088 }1089 1090 /* Get the data. */1091 const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];1092 *pcchLine = pStream->paLines[iLine].cch;1093 *penmEol = pStream->paLines[iLine].enmEol;1094 1095 /* update the stream position. */1096 pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;1097 pStream->iLine = iLine + 1;1098 1099 return pchRet;1100 }1101 1102 /**1103 * Get a line from the stream.1104 *1105 * A line is always delimited by a LF character or the end of the stream. The1106 * delimiter is not included in returned line length, but instead returned via1107 * the @a penmEol indicator.1108 *1109 * @returns Pointer to the first character in the line, not NULL terminated.1110 * NULL if the end of the stream has been reached or some problem1111 * occurred.1112 *1113 * @param pStream The stream. Must be in read mode.1114 * @param pcchLine The length.1115 * @param penmEol Where to return the end of line type indicator.1116 */1117 static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)1118 {1119 /** @todo this doesn't work when pStream->off !=1120 * pStream->paLines[pStream->iLine-1].pff. */1121 if (!pStream->fFullyLineated)1122 return scmStreamGetLineInternal(pStream, pcchLine, penmEol);1123 return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);1124 }1125 1126 /**1127 * Reads @a cbToRead bytes into @a pvBuf.1128 *1129 * Will fail if end of stream is encountered before the entire read has been1130 * completed.1131 *1132 * @returns IPRT status code.1133 * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream1134 * position will be unchanged.1135 *1136 * @param pStream The stream. Must be in read mode.1137 * @param pvBuf The buffer to read into.1138 * @param cbToRead The number of bytes to read.1139 */1140 static int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)1141 {1142 AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);1143 if (RT_FAILURE(pStream->rc))1144 return pStream->rc;1145 1146 /* If there isn't enough stream left, fail already. */1147 if (RT_UNLIKELY(pStream->cb - pStream->cb < cbToRead))1148 return VERR_EOF;1149 1150 /* Copy the data and simply seek to the new stream position. */1151 memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);1152 return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);1153 }1154 1155 /**1156 * Checks if the given line is empty or full of white space.1157 *1158 * @returns true if white space only, false if not (or if non-existant).1159 * @param pStream The stream. Must be in read mode.1160 * @param iLine The line in question.1161 */1162 static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)1163 {1164 SCMEOL enmEol;1165 size_t cchLine;1166 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);1167 if (!pchLine)1168 return false;1169 while (cchLine && RT_C_IS_SPACE(*pchLine))1170 pchLine++, cchLine--;1171 return cchLine == 0;1172 }1173 1174 /**1175 * Try figure out the end of line style of the give stream.1176 *1177 * @returns Most likely end of line style.1178 * @param pStream The stream.1179 */1180 SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)1181 {1182 SCMEOL enmEol;1183 if (pStream->cLines > 0)1184 enmEol = pStream->paLines[0].enmEol;1185 else if (pStream->cb == 0)1186 enmEol = SCMEOL_NONE;1187 else1188 {1189 const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);1190 if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')1191 enmEol = SCMEOL_CRLF;1192 else1193 enmEol = SCMEOL_LF;1194 }1195 1196 if (enmEol == SCMEOL_NONE)1197 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1198 enmEol = SCMEOL_CRLF;1199 #else1200 enmEol = SCMEOL_LF;1201 #endif1202 return enmEol;1203 }1204 1205 /**1206 * Get the end of line indicator type for a line.1207 *1208 * @returns The EOL indicator. If the line isn't found, the default EOL1209 * indicator is return.1210 * @param pStream The stream.1211 * @param iLine The line (0-base).1212 */1213 SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)1214 {1215 SCMEOL enmEol;1216 if (iLine < pStream->cLines)1217 enmEol = pStream->paLines[iLine].enmEol;1218 else1219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1220 enmEol = SCMEOL_CRLF;1221 #else1222 enmEol = SCMEOL_LF;1223 #endif1224 return enmEol;1225 }1226 1227 /**1228 * Appends a line to the stream.1229 *1230 * @returns IPRT status code.1231 * @param pStream The stream. Must be in write mode.1232 * @param pchLine Pointer to the line.1233 * @param cchLine Line length.1234 * @param enmEol Which end of line indicator to use.1235 */1236 int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)1237 {1238 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1239 if (RT_FAILURE(pStream->rc))1240 return pStream->rc;1241 1242 /*1243 * Make sure the previous line has a new-line indicator.1244 */1245 size_t off = pStream->off;1246 size_t iLine = pStream->iLine;1247 if (RT_UNLIKELY( iLine != 01248 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))1249 {1250 AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);1251 SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);1252 if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))1253 {1254 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);1255 if (RT_FAILURE(rc))1256 return rc;1257 }1258 if (enmEol2 == SCMEOL_LF)1259 pStream->pch[off++] = '\n';1260 else1261 {1262 pStream->pch[off++] = '\r';1263 pStream->pch[off++] = '\n';1264 }1265 pStream->paLines[iLine - 1].enmEol = enmEol2;1266 pStream->paLines[iLine].off = off;1267 pStream->off = off;1268 pStream->cb = off;1269 }1270 1271 /*1272 * Ensure we've got sufficient buffer space.1273 */1274 if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))1275 {1276 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);1277 if (RT_FAILURE(rc))1278 return rc;1279 }1280 1281 /*1282 * Add a line record.1283 */1284 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))1285 {1286 int rc = scmStreamGrowLines(pStream, iLine);1287 if (RT_FAILURE(rc))1288 return rc;1289 }1290 1291 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine;1292 pStream->paLines[iLine].enmEol = enmEol;1293 1294 iLine++;1295 pStream->cLines = iLine;1296 pStream->iLine = iLine;1297 1298 /*1299 * Copy the line1300 */1301 memcpy(&pStream->pch[off], pchLine, cchLine);1302 off += cchLine;1303 if (enmEol == SCMEOL_LF)1304 pStream->pch[off++] = '\n';1305 else if (enmEol == SCMEOL_CRLF)1306 {1307 pStream->pch[off++] = '\r';1308 pStream->pch[off++] = '\n';1309 }1310 pStream->off = off;1311 pStream->cb = off;1312 1313 /*1314 * Start a new line.1315 */1316 pStream->paLines[iLine].off = off;1317 pStream->paLines[iLine].cch = 0;1318 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1319 1320 return VINF_SUCCESS;1321 }1322 1323 /**1324 * Writes to the stream.1325 *1326 * @returns IPRT status code1327 * @param pStream The stream. Must be in write mode.1328 * @param pchBuf What to write.1329 * @param cchBuf How much to write.1330 */1331 int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)1332 {1333 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1334 if (RT_FAILURE(pStream->rc))1335 return pStream->rc;1336 1337 /*1338 * Ensure we've got sufficient buffer space.1339 */1340 size_t off = pStream->off;1341 if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))1342 {1343 int rc = scmStreamGrowBuffer(pStream, cchBuf);1344 if (RT_FAILURE(rc))1345 return rc;1346 }1347 1348 /*1349 * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.1350 */1351 size_t iLine = pStream->iLine;1352 if (RT_UNLIKELY( iLine > 01353 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))1354 {1355 iLine--;1356 pStream->cLines = iLine;1357 pStream->iLine = iLine;1358 }1359 1360 /*1361 * Deal with lines.1362 */1363 const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);1364 if (!pchLF)1365 pStream->paLines[iLine].cch += cchBuf;1366 else1367 {1368 const char *pchLine = pchBuf;1369 for (;;)1370 {1371 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))1372 {1373 int rc = scmStreamGrowLines(pStream, iLine);1374 if (RT_FAILURE(rc))1375 {1376 iLine = pStream->iLine;1377 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off;1378 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1379 return rc;1380 }1381 }1382 1383 size_t cchLine = pchLF - pchLine;1384 if ( cchLine1385 ? pchLF[-1] != '\r'1386 : !pStream->paLines[iLine].cch1387 || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')1388 pStream->paLines[iLine].enmEol = SCMEOL_LF;1389 else1390 {1391 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;1392 cchLine--;1393 }1394 pStream->paLines[iLine].cch += cchLine;1395 1396 iLine++;1397 size_t offBuf = pchLF + 1 - pchBuf;1398 pStream->paLines[iLine].off = off + offBuf;1399 pStream->paLines[iLine].cch = 0;1400 pStream->paLines[iLine].enmEol = SCMEOL_NONE;1401 1402 size_t cchLeft = cchBuf - offBuf;1403 pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);1404 if (!pchLF)1405 {1406 pStream->paLines[iLine].cch = cchLeft;1407 break;1408 }1409 }1410 1411 pStream->iLine = iLine;1412 pStream->cLines = iLine;1413 }1414 1415 /*1416 * Copy the data and update position and size.1417 */1418 memcpy(&pStream->pch[off], pchBuf, cchBuf);1419 off += cchBuf;1420 pStream->off = off;1421 pStream->cb = off;1422 1423 return VINF_SUCCESS;1424 }1425 1426 /**1427 * Write a character to the stream.1428 *1429 * @returns IPRT status code1430 * @param pStream The stream. Must be in write mode.1431 * @param pchBuf What to write.1432 * @param cchBuf How much to write.1433 */1434 int ScmStreamPutCh(PSCMSTREAM pStream, char ch)1435 {1436 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);1437 if (RT_FAILURE(pStream->rc))1438 return pStream->rc;1439 1440 /*1441 * Only deal with the simple cases here, use ScmStreamWrite for the1442 * annoying stuff.1443 */1444 size_t off = pStream->off;1445 if ( ch == '\n'1446 || RT_UNLIKELY(off + 1 > pStream->cbAllocated))1447 return ScmStreamWrite(pStream, &ch, 1);1448 1449 /*1450 * Just append it.1451 */1452 pStream->pch[off] = ch;1453 pStream->off = off + 1;1454 pStream->paLines[pStream->iLine].cch++;1455 1456 return VINF_SUCCESS;1457 }1458 1459 /**1460 * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.1461 *1462 * The stream positions will be used and changed in both streams.1463 *1464 * @returns IPRT status code.1465 * @param pDst The destination stream. Must be in write mode.1466 * @param cLines The number of lines. (0 is accepted.)1467 * @param pSrc The source stream. Must be in read mode.1468 */1469 int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)1470 {1471 AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);1472 if (RT_FAILURE(pDst->rc))1473 return pDst->rc;1474 1475 AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);1476 if (RT_FAILURE(pSrc->rc))1477 return pSrc->rc;1478 1479 while (cLines-- > 0)1480 {1481 SCMEOL enmEol;1482 size_t cchLine;1483 const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);1484 if (!pchLine)1485 return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);1486 1487 int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);1488 if (RT_FAILURE(rc))1489 return rc;1490 }1491 1492 return VINF_SUCCESS;1493 }1494 1495 /* -=-=-=-=-=- diff -=-=-=-=-=- */1496 1497 1498 /**1499 * Prints a range of lines with a prefix.1500 *1501 * @param pState The diff state.1502 * @param chPrefix The prefix.1503 * @param pStream The stream to get the lines from.1504 * @param iLine The first line.1505 * @param cLines The number of lines.1506 */1507 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)1508 {1509 while (cLines-- > 0)1510 {1511 SCMEOL enmEol;1512 size_t cchLine;1513 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);1514 1515 RTStrmPutCh(pState->pDiff, chPrefix);1516 if (pchLine && cchLine)1517 {1518 if (!pState->fSpecialChars)1519 RTStrmWrite(pState->pDiff, pchLine, cchLine);1520 else1521 {1522 size_t offVir = 0;1523 const char *pchStart = pchLine;1524 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);1525 while (pchTab)1526 {1527 RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);1528 offVir += pchTab - pchStart;1529 1530 size_t cchTab = pState->cchTab - offVir % pState->cchTab;1531 switch (cchTab)1532 {1533 case 1: RTStrmPutStr(pState->pDiff, "."); break;1534 case 2: RTStrmPutStr(pState->pDiff, ".."); break;1535 case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;1536 case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;1537 case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;1538 default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;1539 }1540 offVir += cchTab;1541 1542 /* next */1543 pchStart = pchTab + 1;1544 pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));1545 }1546 size_t cchLeft = cchLine - (pchStart - pchLine);1547 if (cchLeft)1548 RTStrmWrite(pState->pDiff, pchStart, cchLeft);1549 }1550 }1551 1552 if (!pState->fSpecialChars)1553 RTStrmPutCh(pState->pDiff, '\n');1554 else if (enmEol == SCMEOL_LF)1555 RTStrmPutStr(pState->pDiff, "[LF]\n");1556 else if (enmEol == SCMEOL_CRLF)1557 RTStrmPutStr(pState->pDiff, "[CRLF]\n");1558 else1559 RTStrmPutStr(pState->pDiff, "[NONE]\n");1560 1561 iLine++;1562 }1563 }1564 1565 1566 /**1567 * Reports a difference and propels the streams to the lines following the1568 * resync.1569 *1570 *1571 * @returns New pState->cDiff value (just to return something).1572 * @param pState The diff state. The cDiffs member will be1573 * incremented.1574 * @param cMatches The resync length.1575 * @param iLeft Where the difference starts on the left side.1576 * @param cLeft How long it is on this side. ~(size_t)0 is used1577 * to indicate that it goes all the way to the end.1578 * @param iRight Where the difference starts on the right side.1579 * @param cRight How long it is.1580 */1581 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,1582 size_t iLeft, size_t cLeft,1583 size_t iRight, size_t cRight)1584 {1585 /*1586 * Adjust the input.1587 */1588 if (cLeft == ~(size_t)0)1589 {1590 size_t c = ScmStreamCountLines(pState->pLeft);1591 if (c >= iLeft)1592 cLeft = c - iLeft;1593 else1594 {1595 iLeft = c;1596 cLeft = 0;1597 }1598 }1599 1600 if (cRight == ~(size_t)0)1601 {1602 size_t c = ScmStreamCountLines(pState->pRight);1603 if (c >= iRight)1604 cRight = c - iRight;1605 else1606 {1607 iRight = c;1608 cRight = 0;1609 }1610 }1611 1612 /*1613 * Print header if it's the first difference1614 */1615 if (!pState->cDiffs)1616 RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);1617 1618 /*1619 * Emit the change description.1620 */1621 char ch = cLeft == 01622 ? 'a'1623 : cRight == 01624 ? 'd'1625 : 'c';1626 if (cLeft > 1 && cRight > 1)1627 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);1628 else if (cLeft > 1)1629 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);1630 else if (cRight > 1)1631 RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);1632 else1633 RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);1634 1635 /*1636 * And the lines.1637 */1638 if (cLeft)1639 scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);1640 if (cLeft && cRight)1641 RTStrmPrintf(pState->pDiff, "---\n");1642 if (cRight)1643 scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);1644 1645 /*1646 * Reposition the streams (safely ignores return value).1647 */1648 ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);1649 ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);1650 1651 pState->cDiffs++;1652 return pState->cDiffs;1653 }1654 1655 /**1656 * Helper for scmDiffCompare that takes care of trailing spaces and stuff1657 * like that.1658 */1659 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,1660 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,1661 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)1662 {1663 if (pState->fIgnoreTrailingWhite)1664 {1665 while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))1666 cchLeft--;1667 while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))1668 cchRight--;1669 }1670 1671 if (pState->fIgnoreLeadingWhite)1672 {1673 while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))1674 pchLeft++, cchLeft--;1675 while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))1676 pchRight++, cchRight--;1677 }1678 1679 if ( cchLeft != cchRight1680 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)1681 || memcmp(pchLeft, pchRight, cchLeft))1682 return false;1683 return true;1684 }1685 1686 /**1687 * Compare two lines.1688 *1689 * @returns true if the are equal, false if not.1690 */1691 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,1692 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,1693 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)1694 {1695 if ( cchLeft != cchRight1696 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)1697 || memcmp(pchLeft, pchRight, cchLeft))1698 {1699 if ( pState->fIgnoreTrailingWhite1700 || pState->fIgnoreTrailingWhite)1701 return scmDiffCompareSlow(pState,1702 pchLeft, cchLeft, enmEolLeft,1703 pchRight, cchRight, enmEolRight);1704 return false;1705 }1706 return true;1707 }1708 1709 /**1710 * Compares two sets of lines from the two files.1711 *1712 * @returns true if they matches, false if they don't.1713 * @param pState The diff state.1714 * @param iLeft Where to start in the left stream.1715 * @param iRight Where to start in the right stream.1716 * @param cLines How many lines to compare.1717 */1718 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)1719 {1720 for (size_t iLine = 0; iLine < cLines; iLine++)1721 {1722 SCMEOL enmEolLeft;1723 size_t cchLeft;1724 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);1725 1726 SCMEOL enmEolRight;1727 size_t cchRight;1728 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);1729 1730 if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))1731 return false;1732 }1733 return true;1734 }1735 1736 1737 /**1738 * Resynchronize the two streams and reports the difference.1739 *1740 * Upon return, the streams will be positioned after the block of @a cMatches1741 * lines where it resynchronized them.1742 *1743 * @returns pState->cDiffs (just so we can use it in a return statement).1744 * @param pState The state.1745 * @param cMatches The number of lines that needs to match for the1746 * stream to be considered synchronized again.1747 */1748 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)1749 {1750 size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;1751 size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;1752 Assert(cMatches > 0);1753 1754 /*1755 * Compare each new line from each of the streams will all the preceding1756 * ones, including iStartLeft/Right.1757 */1758 for (size_t iRange = 1; ; iRange++)1759 {1760 /*1761 * Get the next line in the left stream and compare it against all the1762 * preceding lines on the right side.1763 */1764 SCMEOL enmEol;1765 size_t cchLine;1766 const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);1767 if (!pchLine)1768 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);1769 1770 for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)1771 {1772 SCMEOL enmEolRight;1773 size_t cchRight;1774 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,1775 &cchRight, &enmEolRight);1776 if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)1777 && scmDiffCompareLines(pState,1778 iStartLeft + iRange + 1 - cMatches,1779 iStartRight + iRight + 1 - cMatches,1780 cMatches - 1)1781 )1782 return scmDiffReport(pState, cMatches,1783 iStartLeft, iRange + 1 - cMatches,1784 iStartRight, iRight + 1 - cMatches);1785 }1786 1787 /*1788 * Get the next line in the right stream and compare it against all the1789 * lines on the right side.1790 */1791 pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);1792 if (!pchLine)1793 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);1794 1795 for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)1796 {1797 SCMEOL enmEolLeft;1798 size_t cchLeft;1799 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,1800 &cchLeft, &enmEolLeft);1801 if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)1802 && scmDiffCompareLines(pState,1803 iStartLeft + iLeft + 1 - cMatches,1804 iStartRight + iRange + 1 - cMatches,1805 cMatches - 1)1806 )1807 return scmDiffReport(pState, cMatches,1808 iStartLeft, iLeft + 1 - cMatches,1809 iStartRight, iRange + 1 - cMatches);1810 }1811 }1812 }1813 1814 /**1815 * Creates a diff of the changes between the streams @a pLeft and @a pRight.1816 *1817 * This currently only implements the simplest diff format, so no contexts.1818 *1819 * Also, note that we won't detect differences in the final newline of the1820 * streams.1821 *1822 * @returns The number of differences.1823 * @param pszFilename The filename.1824 * @param pLeft The left side stream.1825 * @param pRight The right side stream.1826 * @param fIgnoreEol Whether to ignore end of line markers.1827 * @param fIgnoreLeadingWhite Set if leading white space should be ignored.1828 * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.1829 * @param fSpecialChars Whether to print special chars in a human1830 * readable form or not.1831 * @param cchTab The tab size.1832 * @param pDiff Where to write the diff.1833 */1834 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,1835 bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,1836 size_t cchTab, PRTSTREAM pDiff)1837 {1838 #ifdef RT_STRICT1839 ScmStreamCheckItegrity(pLeft);1840 ScmStreamCheckItegrity(pRight);1841 #endif1842 1843 /*1844 * Set up the diff state.1845 */1846 SCMDIFFSTATE State;1847 State.cDiffs = 0;1848 State.pszFilename = pszFilename;1849 State.pLeft = pLeft;1850 State.pRight = pRight;1851 State.fIgnoreEol = fIgnoreEol;1852 State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;1853 State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;1854 State.fSpecialChars = fSpecialChars;1855 State.cchTab = cchTab;1856 State.pDiff = pDiff;1857 1858 /*1859 * Compare them line by line.1860 */1861 ScmStreamRewindForReading(pLeft);1862 ScmStreamRewindForReading(pRight);1863 const char *pchLeft;1864 const char *pchRight;1865 1866 for (;;)1867 {1868 SCMEOL enmEolLeft;1869 size_t cchLeft;1870 pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);1871 1872 SCMEOL enmEolRight;1873 size_t cchRight;1874 pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);1875 if (!pchLeft || !pchRight)1876 break;1877 1878 if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))1879 scmDiffSynchronize(&State, 3);1880 }1881 1882 /*1883 * Deal with any remaining differences.1884 */1885 if (pchLeft)1886 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);1887 else if (pchRight)1888 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);1889 1890 /*1891 * Report any errors.1892 */1893 if (RT_FAILURE(ScmStreamGetStatus(pLeft)))1894 RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));1895 if (RT_FAILURE(ScmStreamGetStatus(pRight)))1896 RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));1897 1898 return State.cDiffs;1899 }1900 1901 1902 1903 /* -=-=-=-=-=- settings -=-=-=-=-=- */1904 1905 /**1906 * Init a settings structure with settings from @a pSrc.1907 *1908 * @returns IPRT status code1909 * @param pSettings The settings.1910 * @param pSrc The source settings.1911 */1912 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)1913 {1914 *pSettings = *pSrc;1915 1916 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);1917 if (RT_SUCCESS(rc))1918 {1919 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);1920 if (RT_SUCCESS(rc))1921 {1922 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);1923 if (RT_SUCCESS(rc))1924 return VINF_SUCCESS;1925 1926 RTStrFree(pSettings->pszFilterOutFiles);1927 }1928 RTStrFree(pSettings->pszFilterFiles);1929 }1930 1931 pSettings->pszFilterFiles = NULL;1932 pSettings->pszFilterOutFiles = NULL;1933 pSettings->pszFilterOutDirs = NULL;1934 return rc;1935 }1936 1937 /**1938 * Init a settings structure.1939 *1940 * @returns IPRT status code1941 * @param pSettings The settings.1942 */1943 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)1944 {1945 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);1946 }1947 1948 /**1949 * Deletes the settings, i.e. free any dynamically allocated content.1950 *1951 * @param pSettings The settings.1952 */1953 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)1954 {1955 if (pSettings)1956 {1957 Assert(pSettings->cchTab != ~(unsigned)0);1958 pSettings->cchTab = ~(unsigned)0;1959 1960 RTStrFree(pSettings->pszFilterFiles);1961 pSettings->pszFilterFiles = NULL;1962 1963 RTStrFree(pSettings->pszFilterOutFiles);1964 pSettings->pszFilterOutFiles = NULL;1965 1966 RTStrFree(pSettings->pszFilterOutDirs);1967 pSettings->pszFilterOutDirs = NULL;1968 }1969 }1970 1971 1972 /**1973 * Processes a RTGetOpt result.1974 *1975 * @retval VINF_SUCCESS if handled.1976 * @retval VERR_OUT_OF_RANGE if the option value was out of range.1977 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.1978 *1979 * @param pSettings The settings to change.1980 * @param rc The RTGetOpt return value.1981 * @param pValueUnion The RTGetOpt value union.1982 */1983 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)1984 {1985 switch (rc)1986 {1987 case SCMOPT_CONVERT_EOL:1988 pSettings->fConvertEol = true;1989 return VINF_SUCCESS;1990 case SCMOPT_NO_CONVERT_EOL:1991 pSettings->fConvertEol = false;1992 return VINF_SUCCESS;1993 1994 case SCMOPT_CONVERT_TABS:1995 pSettings->fConvertTabs = true;1996 return VINF_SUCCESS;1997 case SCMOPT_NO_CONVERT_TABS:1998 pSettings->fConvertTabs = false;1999 return VINF_SUCCESS;2000 2001 case SCMOPT_FORCE_FINAL_EOL:2002 pSettings->fForceFinalEol = true;2003 return VINF_SUCCESS;2004 case SCMOPT_NO_FORCE_FINAL_EOL:2005 pSettings->fForceFinalEol = false;2006 return VINF_SUCCESS;2007 2008 case SCMOPT_FORCE_TRAILING_LINE:2009 pSettings->fForceTrailingLine = true;2010 return VINF_SUCCESS;2011 case SCMOPT_NO_FORCE_TRAILING_LINE:2012 pSettings->fForceTrailingLine = false;2013 return VINF_SUCCESS;2014 2015 case SCMOPT_STRIP_TRAILING_BLANKS:2016 pSettings->fStripTrailingBlanks = true;2017 return VINF_SUCCESS;2018 case SCMOPT_NO_STRIP_TRAILING_BLANKS:2019 pSettings->fStripTrailingBlanks = false;2020 return VINF_SUCCESS;2021 2022 case SCMOPT_STRIP_TRAILING_LINES:2023 pSettings->fStripTrailingLines = true;2024 return VINF_SUCCESS;2025 case SCMOPT_NO_STRIP_TRAILING_LINES:2026 pSettings->fStripTrailingLines = false;2027 return VINF_SUCCESS;2028 2029 case SCMOPT_ONLY_SVN_DIRS:2030 pSettings->fOnlySvnDirs = true;2031 return VINF_SUCCESS;2032 case SCMOPT_NOT_ONLY_SVN_DIRS:2033 pSettings->fOnlySvnDirs = false;2034 return VINF_SUCCESS;2035 2036 case SCMOPT_ONLY_SVN_FILES:2037 pSettings->fOnlySvnFiles = true;2038 return VINF_SUCCESS;2039 case SCMOPT_NOT_ONLY_SVN_FILES:2040 pSettings->fOnlySvnFiles = false;2041 return VINF_SUCCESS;2042 2043 case SCMOPT_SET_SVN_EOL:2044 pSettings->fSetSvnEol = true;2045 return VINF_SUCCESS;2046 case SCMOPT_DONT_SET_SVN_EOL:2047 pSettings->fSetSvnEol = false;2048 return VINF_SUCCESS;2049 2050 case SCMOPT_SET_SVN_EXECUTABLE:2051 pSettings->fSetSvnExecutable = true;2052 return VINF_SUCCESS;2053 case SCMOPT_DONT_SET_SVN_EXECUTABLE:2054 pSettings->fSetSvnExecutable = false;2055 return VINF_SUCCESS;2056 2057 case SCMOPT_SET_SVN_KEYWORDS:2058 pSettings->fSetSvnKeywords = true;2059 return VINF_SUCCESS;2060 case SCMOPT_DONT_SET_SVN_KEYWORDS:2061 pSettings->fSetSvnKeywords = false;2062 return VINF_SUCCESS;2063 2064 case SCMOPT_TAB_SIZE:2065 if ( pValueUnion->u8 < 12066 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))2067 {2068 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",2069 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);2070 return VERR_OUT_OF_RANGE;2071 }2072 pSettings->cchTab = pValueUnion->u8;2073 return VINF_SUCCESS;2074 2075 case SCMOPT_FILTER_OUT_DIRS:2076 case SCMOPT_FILTER_FILES:2077 case SCMOPT_FILTER_OUT_FILES:2078 {2079 char **ppsz = NULL;2080 switch (rc)2081 {2082 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;2083 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;2084 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;2085 }2086 2087 /*2088 * An empty string zaps the current list.2089 */2090 if (!*pValueUnion->psz)2091 return RTStrATruncate(ppsz, 0);2092 2093 /*2094 * Non-empty strings are appended to the pattern list.2095 *2096 * Strip leading and trailing pattern separators before attempting2097 * to append it. If it's just separators, don't do anything.2098 */2099 const char *pszSrc = pValueUnion->psz;2100 while (*pszSrc == '|')2101 pszSrc++;2102 size_t cchSrc = strlen(pszSrc);2103 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')2104 cchSrc--;2105 if (!cchSrc)2106 return VINF_SUCCESS;2107 2108 return RTStrAAppendExN(ppsz, 2,2109 "|", *ppsz && **ppsz ? 1 : 0,2110 pszSrc, cchSrc);2111 }2112 2113 default:2114 return VERR_GETOPT_UNKNOWN_OPTION;2115 }2116 }2117 2118 /**2119 * Parses an option string.2120 *2121 * @returns IPRT status code.2122 * @param pBase The base settings structure to apply the options2123 * to.2124 * @param pszOptions The options to parse.2125 */2126 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)2127 {2128 int cArgs;2129 char **papszArgs;2130 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);2131 if (RT_SUCCESS(rc))2132 {2133 RTGETOPTUNION ValueUnion;2134 RTGETOPTSTATE GetOptState;2135 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);2136 if (RT_SUCCESS(rc))2137 {2138 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)2139 {2140 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);2141 if (RT_FAILURE(rc))2142 break;2143 }2144 }2145 RTGetOptArgvFree(papszArgs);2146 }2147 2148 return rc;2149 }2150 2151 /**2152 * Parses an unterminated option string.2153 *2154 * @returns IPRT status code.2155 * @param pBase The base settings structure to apply the options2156 * to.2157 * @param pchLine The line.2158 * @param cchLine The line length.2159 */2160 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)2161 {2162 char *pszLine = RTStrDupN(pchLine, cchLine);2163 if (!pszLine)2164 return VERR_NO_MEMORY;2165 int rc = scmSettingsBaseParseString(pBase, pszLine);2166 RTStrFree(pszLine);2167 return rc;2168 }2169 2170 /**2171 * Verifies the options string.2172 *2173 * @returns IPRT status code.2174 * @param pszOptions The options to verify .2175 */2176 static int scmSettingsBaseVerifyString(const char *pszOptions)2177 {2178 SCMSETTINGSBASE Base;2179 int rc = scmSettingsBaseInit(&Base);2180 if (RT_SUCCESS(rc))2181 {2182 rc = scmSettingsBaseParseString(&Base, pszOptions);2183 scmSettingsBaseDelete(&Base);2184 }2185 return rc;2186 }2187 2188 /**2189 * Loads settings found in editor and SCM settings directives within the2190 * document (@a pStream).2191 *2192 * @returns IPRT status code.2193 * @param pBase The settings base to load settings into.2194 * @param pStream The stream to scan for settings directives.2195 */2196 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)2197 {2198 /** @todo Editor and SCM settings directives in documents. */2199 return VINF_SUCCESS;2200 }2201 2202 /**2203 * Creates a new settings file struct, cloning @a pSettings.2204 *2205 * @returns IPRT status code.2206 * @param ppSettings Where to return the new struct.2207 * @param pSettingsBase The settings to inherit from.2208 */2209 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)2210 {2211 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));2212 if (!pSettings)2213 return VERR_NO_MEMORY;2214 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);2215 if (RT_SUCCESS(rc))2216 {2217 pSettings->pDown = NULL;2218 pSettings->pUp = NULL;2219 pSettings->paPairs = NULL;2220 pSettings->cPairs = 0;2221 *ppSettings = pSettings;2222 return VINF_SUCCESS;2223 }2224 RTMemFree(pSettings);2225 return rc;2226 }2227 2228 /**2229 * Destroys a settings structure.2230 *2231 * @param pSettings The settings structure to destroy. NULL is OK.2232 */2233 static void scmSettingsDestroy(PSCMSETTINGS pSettings)2234 {2235 if (pSettings)2236 {2237 scmSettingsBaseDelete(&pSettings->Base);2238 for (size_t i = 0; i < pSettings->cPairs; i++)2239 {2240 RTStrFree(pSettings->paPairs[i].pszPattern);2241 RTStrFree(pSettings->paPairs[i].pszOptions);2242 pSettings->paPairs[i].pszPattern = NULL;2243 pSettings->paPairs[i].pszOptions = NULL;2244 }2245 RTMemFree(pSettings->paPairs);2246 pSettings->paPairs = NULL;2247 RTMemFree(pSettings);2248 }2249 }2250 2251 /**2252 * Adds a pattern/options pair to the settings structure.2253 *2254 * @returns IPRT status code.2255 * @param pSettings The settings.2256 * @param pchLine The line containing the unparsed pair.2257 * @param cchLine The length of the line.2258 */2259 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)2260 {2261 /*2262 * Split the string.2263 */2264 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);2265 if (!pchOptions)2266 return VERR_INVALID_PARAMETER;2267 size_t cchPattern = pchOptions - pchLine;2268 size_t cchOptions = cchLine - cchPattern - 1;2269 pchOptions++;2270 2271 /* strip spaces everywhere */2272 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))2273 cchPattern--;2274 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))2275 cchPattern--, pchLine++;2276 2277 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))2278 cchOptions--;2279 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))2280 cchOptions--, pchOptions++;2281 2282 /* Quietly ignore empty patterns and empty options. */2283 if (!cchOptions || !cchPattern)2284 return VINF_SUCCESS;2285 2286 /*2287 * Add the pair and verify the option string.2288 */2289 uint32_t iPair = pSettings->cPairs;2290 if ((iPair % 32) == 0)2291 {2292 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));2293 if (!pvNew)2294 return VERR_NO_MEMORY;2295 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;2296 }2297 2298 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);2299 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);2300 int rc;2301 if ( pSettings->paPairs[iPair].pszPattern2302 && pSettings->paPairs[iPair].pszOptions)2303 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);2304 else2305 rc = VERR_NO_MEMORY;2306 if (RT_SUCCESS(rc))2307 pSettings->cPairs = iPair + 1;2308 else2309 {2310 RTStrFree(pSettings->paPairs[iPair].pszPattern);2311 RTStrFree(pSettings->paPairs[iPair].pszOptions);2312 }2313 return rc;2314 }2315 2316 /**2317 * Loads in the settings from @a pszFilename.2318 *2319 * @returns IPRT status code.2320 * @param pSettings Where to load the settings file.2321 * @param pszFilename The file to load.2322 */2323 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)2324 {2325 SCMSTREAM Stream;2326 int rc = ScmStreamInitForReading(&Stream, pszFilename);2327 if (RT_FAILURE(rc))2328 {2329 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);2330 return rc;2331 }2332 2333 SCMEOL enmEol;2334 const char *pchLine;2335 size_t cchLine;2336 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)2337 {2338 /* Ignore leading spaces. */2339 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))2340 pchLine++, cchLine--;2341 2342 /* Ignore empty lines and comment lines. */2343 if (cchLine < 1 || *pchLine == '#')2344 continue;2345 2346 /* What kind of line is it? */2347 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);2348 if (pchColon)2349 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);2350 else2351 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);2352 if (RT_FAILURE(rc))2353 {2354 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);2355 break;2356 }2357 }2358 2359 if (RT_SUCCESS(rc))2360 {2361 rc = ScmStreamGetStatus(&Stream);2362 if (RT_FAILURE(rc))2363 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);2364 }2365 2366 ScmStreamDelete(&Stream);2367 return rc;2368 }2369 2370 /**2371 * Parse the specified settings file creating a new settings struct from it.2372 *2373 * @returns IPRT status code2374 * @param ppSettings Where to return the new settings.2375 * @param pszFilename The file to parse.2376 * @param pSettingsBase The base settings we inherit from.2377 */2378 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)2379 {2380 PSCMSETTINGS pSettings;2381 int rc = scmSettingsCreate(&pSettings, pSettingsBase);2382 if (RT_SUCCESS(rc))2383 {2384 rc = scmSettingsLoadFile(pSettings, pszFilename);2385 if (RT_SUCCESS(rc))2386 {2387 *ppSettings = pSettings;2388 return VINF_SUCCESS;2389 }2390 2391 scmSettingsDestroy(pSettings);2392 }2393 *ppSettings = NULL;2394 return rc;2395 }2396 2397 2398 /**2399 * Create an initial settings structure when starting processing a new file or2400 * directory.2401 *2402 * This will look for .scm-settings files from the root and down to the2403 * specified directory, combining them into the returned settings structure.2404 *2405 * @returns IPRT status code.2406 * @param ppSettings Where to return the pointer to the top stack2407 * object.2408 * @param pBaseSettings The base settings we inherit from (globals2409 * typically).2410 * @param pszPath The absolute path to the new directory or file.2411 */2412 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)2413 {2414 *ppSettings = NULL; /* try shut up gcc. */2415 2416 /*2417 * We'll be working with a stack copy of the path.2418 */2419 char szFile[RTPATH_MAX];2420 size_t cchDir = strlen(pszPath);2421 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))2422 return VERR_FILENAME_TOO_LONG;2423 2424 /*2425 * Create the bottom-most settings.2426 */2427 PSCMSETTINGS pSettings;2428 int rc = scmSettingsCreate(&pSettings, pBaseSettings);2429 if (RT_FAILURE(rc))2430 return rc;2431 2432 /*2433 * Enumerate the path components from the root and down. Load any setting2434 * files we find.2435 */2436 size_t cComponents = RTPathCountComponents(pszPath);2437 for (size_t i = 1; i <= cComponents; i++)2438 {2439 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);2440 if (RT_SUCCESS(rc))2441 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);2442 if (RT_FAILURE(rc))2443 break;2444 if (RTFileExists(szFile))2445 {2446 rc = scmSettingsLoadFile(pSettings, szFile);2447 if (RT_FAILURE(rc))2448 break;2449 }2450 }2451 2452 if (RT_SUCCESS(rc))2453 *ppSettings = pSettings;2454 else2455 scmSettingsDestroy(pSettings);2456 return rc;2457 }2458 2459 /**2460 * Pushes a new settings set onto the stack.2461 *2462 * @param ppSettingsStack The pointer to the pointer to the top stack2463 * element. This will be used as input and output.2464 * @param pSettings The settings to push onto the stack.2465 */2466 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)2467 {2468 PSCMSETTINGS pOld = *ppSettingsStack;2469 pSettings->pDown = pOld;2470 pSettings->pUp = NULL;2471 if (pOld)2472 pOld->pUp = pSettings;2473 *ppSettingsStack = pSettings;2474 }2475 2476 /**2477 * Pushes the settings of the specified directory onto the stack.2478 *2479 * We will load any .scm-settings in the directory. A stack entry is added even2480 * if no settings file was found.2481 *2482 * @returns IPRT status code.2483 * @param ppSettingsStack The pointer to the pointer to the top stack2484 * element. This will be used as input and output.2485 * @param pszDir The directory to do this for.2486 */2487 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)2488 {2489 char szFile[RTPATH_MAX];2490 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);2491 if (RT_SUCCESS(rc))2492 {2493 PSCMSETTINGS pSettings;2494 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);2495 if (RT_SUCCESS(rc))2496 {2497 if (RTFileExists(szFile))2498 rc = scmSettingsLoadFile(pSettings, szFile);2499 if (RT_SUCCESS(rc))2500 {2501 scmSettingsStackPush(ppSettingsStack, pSettings);2502 return VINF_SUCCESS;2503 }2504 2505 scmSettingsDestroy(pSettings);2506 }2507 }2508 return rc;2509 }2510 2511 2512 /**2513 * Pops a settings set off the stack.2514 *2515 * @returns The popped setttings.2516 * @param ppSettingsStack The pointer to the pointer to the top stack2517 * element. This will be used as input and output.2518 */2519 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)2520 {2521 PSCMSETTINGS pRet = *ppSettingsStack;2522 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;2523 *ppSettingsStack = pNew;2524 if (pNew)2525 pNew->pUp = NULL;2526 if (pRet)2527 {2528 pRet->pUp = NULL;2529 pRet->pDown = NULL;2530 }2531 return pRet;2532 }2533 2534 /**2535 * Pops and destroys the top entry of the stack.2536 *2537 * @param ppSettingsStack The pointer to the pointer to the top stack2538 * element. This will be used as input and output.2539 */2540 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)2541 {2542 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));2543 }2544 2545 /**2546 * Constructs the base settings for the specified file name.2547 *2548 * @returns IPRT status code.2549 * @param pSettingsStack The top element on the settings stack.2550 * @param pszFilename The file name.2551 * @param pszBasename The base name (pointer within @a pszFilename).2552 * @param cchBasename The length of the base name. (For passing to2553 * RTStrSimplePatternMultiMatch.)2554 * @param pBase Base settings to initialize.2555 */2556 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,2557 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)2558 {2559 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);2560 if (RT_SUCCESS(rc))2561 {2562 /* find the bottom entry in the stack. */2563 PCSCMSETTINGS pCur = pSettingsStack;2564 while (pCur->pDown)2565 pCur = pCur->pDown;2566 2567 /* Work our way up thru the stack and look for matching pairs. */2568 while (pCur)2569 {2570 size_t const cPairs = pCur->cPairs;2571 if (cPairs)2572 {2573 for (size_t i = 0; i < cPairs; i++)2574 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,2575 pszBasename, cchBasename, NULL)2576 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,2577 pszFilename, RTSTR_MAX, NULL))2578 {2579 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);2580 if (RT_FAILURE(rc))2581 break;2582 }2583 if (RT_FAILURE(rc))2584 break;2585 }2586 2587 /* advance */2588 pCur = pCur->pUp;2589 }2590 }2591 if (RT_FAILURE(rc))2592 scmSettingsBaseDelete(pBase);2593 return rc;2594 }2595 2596 2597 /* -=-=-=-=-=- misc -=-=-=-=-=- */2598 2599 2600 /**2601 * Prints a verbose message if the level is high enough.2602 *2603 * @param pState The rewrite state. Optional.2604 * @param iLevel The required verbosity level.2605 * @param pszFormat The message format string. Can be NULL if we2606 * only want to trigger the per file message.2607 * @param ... Format arguments.2608 */2609 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)2610 {2611 if (iLevel <= g_iVerbosity)2612 {2613 if (pState && !pState->fFirst)2614 {2615 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);2616 pState->fFirst = true;2617 }2618 if (pszFormat)2619 {2620 RTPrintf(pState2621 ? "%s: info: "2622 : "%s: info: ",2623 g_szProgName);2624 va_list va;2625 va_start(va, pszFormat);2626 RTPrintfV(pszFormat, va);2627 va_end(va);2628 }2629 }2630 }2631 2632 2633 /* -=-=-=-=-=- subversion -=-=-=-=-=- */2634 2635 #define SCM_WITHOUT_LIBSVN2636 2637 #ifdef SCM_WITHOUT_LIBSVN2638 2639 /**2640 * Callback that is call for each path to search.2641 */2642 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)2643 {2644 char *pszDst = (char *)pvUser1;2645 size_t cchDst = (size_t)pvUser2;2646 if (cchDst > cchPath)2647 {2648 memcpy(pszDst, pchPath, cchPath);2649 pszDst[cchPath] = '\0';2650 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)2651 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");2652 #else2653 int rc = RTPathAppend(pszDst, cchDst, "svn");2654 #endif2655 if ( RT_SUCCESS(rc)2656 && RTFileExists(pszDst))2657 return VINF_SUCCESS;2658 }2659 return VERR_TRY_AGAIN;2660 }2661 2662 2663 /**2664 * Finds the svn binary.2665 *2666 * @param pszPath Where to store it. Worst case, we'll return2667 * "svn" here.2668 * @param cchPath The size of the buffer pointed to by @a pszPath.2669 */2670 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)2671 {2672 /** @todo code page fun... */2673 Assert(cchPath >= sizeof("svn"));2674 #ifdef RT_OS_WINDOWS2675 const char *pszEnvVar = RTEnvGet("Path");2676 #else2677 const char *pszEnvVar = RTEnvGet("PATH");2678 #endif2679 if (pszPath)2680 {2681 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)2682 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);2683 #else2684 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);2685 #endif2686 if (RT_SUCCESS(rc))2687 return;2688 }2689 strcpy(pszPath, "svn");2690 }2691 2692 2693 /**2694 * Construct a dot svn filename for the file being rewritten.2695 *2696 * @returns IPRT status code.2697 * @param pState The rewrite state (for the name).2698 * @param pszDir The directory, including ".svn/".2699 * @param pszSuff The filename suffix.2700 * @param pszDst The output buffer. RTPATH_MAX in size.2701 */2702 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)2703 {2704 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */2705 RTPathStripFilename(pszDst);2706 2707 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);2708 if (RT_SUCCESS(rc))2709 {2710 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));2711 if (RT_SUCCESS(rc))2712 {2713 size_t cchDst = strlen(pszDst);2714 size_t cchSuff = strlen(pszSuff);2715 if (cchDst + cchSuff < RTPATH_MAX)2716 {2717 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);2718 return VINF_SUCCESS;2719 }2720 else2721 rc = VERR_BUFFER_OVERFLOW;2722 }2723 }2724 return rc;2725 }2726 2727 /**2728 * Interprets the specified string as decimal numbers.2729 *2730 * @returns true if parsed successfully, false if not.2731 * @param pch The string (not terminated).2732 * @param cch The string length.2733 * @param pu Where to return the value.2734 */2735 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)2736 {2737 size_t u = 0;2738 while (cch-- > 0)2739 {2740 char ch = *pch++;2741 if (ch < '0' || ch > '9')2742 return false;2743 u *= 10;2744 u += ch - '0';2745 }2746 *pu = u;2747 return true;2748 }2749 2750 #endif /* SCM_WITHOUT_LIBSVN */2751 2752 /**2753 * Checks if the file we're operating on is part of a SVN working copy.2754 *2755 * @returns true if it is, false if it isn't or we cannot tell.2756 * @param pState The rewrite state to work on.2757 */2758 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)2759 {2760 #ifdef SCM_WITHOUT_LIBSVN2761 /*2762 * Hack: check if the .svn/text-base/<file>.svn-base file exists.2763 */2764 char szPath[RTPATH_MAX];2765 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);2766 if (RT_SUCCESS(rc))2767 return RTFileExists(szPath);2768 2769 #else2770 NOREF(pState);2771 #endif2772 return false;2773 }2774 2775 /**2776 * Queries the value of an SVN property.2777 *2778 * This will automatically adjust for scheduled changes.2779 *2780 * @returns IPRT status code.2781 * @retval VERR_INVALID_STATE if not a SVN WC file.2782 * @retval VERR_NOT_FOUND if the property wasn't found.2783 * @param pState The rewrite state to work on.2784 * @param pszName The property name.2785 * @param ppszValue Where to return the property value. Free this2786 * using RTStrFree. Optional.2787 */2788 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)2789 {2790 /*2791 * Look it up in the scheduled changes.2792 */2793 uint32_t i = pState->cSvnPropChanges;2794 while (i-- > 0)2795 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))2796 {2797 const char *pszValue = pState->paSvnPropChanges[i].pszValue;2798 if (!pszValue)2799 return VERR_NOT_FOUND;2800 if (ppszValue)2801 return RTStrDupEx(ppszValue, pszValue);2802 return VINF_SUCCESS;2803 }2804 2805 #ifdef SCM_WITHOUT_LIBSVN2806 /*2807 * Hack: Read the .svn/props/<file>.svn-work file exists.2808 */2809 char szPath[RTPATH_MAX];2810 int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);2811 if (RT_SUCCESS(rc) && !RTFileExists(szPath))2812 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);2813 if (RT_SUCCESS(rc))2814 {2815 SCMSTREAM Stream;2816 rc = ScmStreamInitForReading(&Stream, szPath);2817 if (RT_SUCCESS(rc))2818 {2819 /*2820 * The current format is K len\n<name>\nV len\n<value>\n" ... END.2821 */2822 rc = VERR_NOT_FOUND;2823 size_t const cchName = strlen(pszName);2824 SCMEOL enmEol;2825 size_t cchLine;2826 const char *pchLine;2827 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)2828 {2829 /*2830 * Parse the 'K num' / 'END' line.2831 */2832 if ( cchLine == 32833 && !memcmp(pchLine, "END", 3))2834 break;2835 size_t cchKey;2836 if ( cchLine < 32837 || pchLine[0] != 'K'2838 || pchLine[1] != ' '2839 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)2840 || cchKey == 02841 || cchKey > 4096)2842 {2843 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);2844 rc = VERR_PARSE_ERROR;2845 break;2846 }2847 2848 /*2849 * Match the key and skip to the value line. Don't bother with2850 * names containing EOL markers.2851 */2852 size_t const offKey = ScmStreamTell(&Stream);2853 bool fMatch = cchName == cchKey;2854 if (fMatch)2855 {2856 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);2857 if (!pchLine)2858 break;2859 fMatch = cchLine == cchName2860 && !memcmp(pchLine, pszName, cchName);2861 }2862 2863 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))2864 break;2865 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))2866 break;2867 2868 /*2869 * Read and Parse the 'V num' line.2870 */2871 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);2872 if (!pchLine)2873 break;2874 size_t cchValue;2875 if ( cchLine < 32876 || pchLine[0] != 'V'2877 || pchLine[1] != ' '2878 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)2879 || cchValue > _1M)2880 {2881 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);2882 rc = VERR_PARSE_ERROR;2883 break;2884 }2885 2886 /*2887 * If we have a match, allocate a return buffer and read the2888 * value into it. Otherwise skip this value and continue2889 * searching.2890 */2891 if (fMatch)2892 {2893 if (!ppszValue)2894 rc = VINF_SUCCESS;2895 else2896 {2897 char *pszValue;2898 rc = RTStrAllocEx(&pszValue, cchValue + 1);2899 if (RT_SUCCESS(rc))2900 {2901 rc = ScmStreamRead(&Stream, pszValue, cchValue);2902 if (RT_SUCCESS(rc))2903 *ppszValue = pszValue;2904 else2905 RTStrFree(pszValue);2906 }2907 }2908 break;2909 }2910 2911 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))2912 break;2913 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))2914 break;2915 }2916 2917 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))2918 {2919 rc = ScmStreamGetStatus(&Stream);2920 RTMsgError("%s: stream error %Rrc\n", szPath, rc);2921 }2922 ScmStreamDelete(&Stream);2923 }2924 }2925 2926 if (rc == VERR_FILE_NOT_FOUND)2927 rc = VERR_NOT_FOUND;2928 return rc;2929 2930 #else2931 NOREF(pState);2932 #endif2933 return VERR_NOT_FOUND;2934 }2935 2936 2937 /**2938 * Schedules the setting of a property.2939 *2940 * @returns IPRT status code.2941 * @retval VERR_INVALID_STATE if not a SVN WC file.2942 * @param pState The rewrite state to work on.2943 * @param pszName The name of the property to set.2944 * @param pszValue The value. NULL means deleting it.2945 */2946 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)2947 {2948 /*2949 * Update any existing entry first.2950 */2951 size_t i = pState->cSvnPropChanges;2952 while (i-- > 0)2953 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))2954 {2955 if (!pszValue)2956 {2957 RTStrFree(pState->paSvnPropChanges[i].pszValue);2958 pState->paSvnPropChanges[i].pszValue = NULL;2959 }2960 else2961 {2962 char *pszCopy;2963 int rc = RTStrDupEx(&pszCopy, pszValue);2964 if (RT_FAILURE(rc))2965 return rc;2966 pState->paSvnPropChanges[i].pszValue = pszCopy;2967 }2968 return VINF_SUCCESS;2969 }2970 2971 /*2972 * Insert a new entry.2973 */2974 i = pState->cSvnPropChanges;2975 if ((i % 32) == 0)2976 {2977 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));2978 if (!pvNew)2979 return VERR_NO_MEMORY;2980 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;2981 }2982 2983 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);2984 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;2985 if ( pState->paSvnPropChanges[i].pszName2986 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )2987 pState->cSvnPropChanges = i + 1;2988 else2989 {2990 RTStrFree(pState->paSvnPropChanges[i].pszName);2991 pState->paSvnPropChanges[i].pszName = NULL;2992 RTStrFree(pState->paSvnPropChanges[i].pszValue);2993 pState->paSvnPropChanges[i].pszValue = NULL;2994 return VERR_NO_MEMORY;2995 }2996 return VINF_SUCCESS;2997 }2998 2999 3000 /**3001 * Schedules a property deletion.3002 *3003 * @returns IPRT status code.3004 * @param pState The rewrite state to work on.3005 * @param pszName The name of the property to delete.3006 */3007 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)3008 {3009 return scmSvnSetProperty(pState, pszName, NULL);3010 }3011 3012 3013 /**3014 * Applies any SVN property changes to the work copy of the file.3015 *3016 * @returns IPRT status code.3017 * @param pState The rewrite state which SVN property changes3018 * should be applied.3019 */3020 static int scmSvnDisplayChanges(PSCMRWSTATE pState)3021 {3022 size_t i = pState->cSvnPropChanges;3023 while (i-- > 0)3024 {3025 const char *pszName = pState->paSvnPropChanges[i].pszName;3026 const char *pszValue = pState->paSvnPropChanges[i].pszValue;3027 if (pszValue)3028 ScmVerbose(pState, 0, "svn ps '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);3029 else3030 ScmVerbose(pState, 0, "svn pd '%s' %s\n", pszName, pszValue, pState->pszFilename);3031 }3032 3033 return VINF_SUCCESS;3034 }3035 3036 /**3037 * Applies any SVN property changes to the work copy of the file.3038 *3039 * @returns IPRT status code.3040 * @param pState The rewrite state which SVN property changes3041 * should be applied.3042 */3043 static int scmSvnApplyChanges(PSCMRWSTATE pState)3044 {3045 #ifdef SCM_WITHOUT_LIBSVN3046 /*3047 * This sucks. We gotta find svn(.exe).3048 */3049 static char s_szSvnPath[RTPATH_MAX];3050 if (s_szSvnPath[0] == '\0')3051 scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));3052 3053 /*3054 * Iterate thru the changes and apply them by starting the svn client.3055 */3056 for (size_t i = 0; i <pState->cSvnPropChanges; i++)3057 {3058 const char *apszArgv[6];3059 apszArgv[0] = s_szSvnPath;3060 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";3061 apszArgv[2] = pState->paSvnPropChanges[i].pszName;3062 int iArg = 3;3063 if (pState->paSvnPropChanges[i].pszValue)3064 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;3065 apszArgv[iArg++] = pState->pszFilename;3066 apszArgv[iArg++] = NULL;3067 ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",3068 apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);3069 3070 RTPROCESS pid;3071 int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);3072 if (RT_SUCCESS(rc))3073 {3074 RTPROCSTATUS Status;3075 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);3076 if ( RT_SUCCESS(rc)3077 && ( Status.enmReason != RTPROCEXITREASON_NORMAL3078 || Status.iStatus != 0) )3079 {3080 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",3081 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],3082 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"3083 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"3084 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"3085 : "abducted by alien",3086 Status.iStatus);3087 return VERR_GENERAL_FAILURE;3088 }3089 }3090 if (RT_FAILURE(rc))3091 {3092 RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",3093 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);3094 return rc;3095 }3096 }3097 3098 return VINF_SUCCESS;3099 #else3100 return VERR_NOT_IMPLEMENTED;3101 #endif3102 }3103 3104 3105 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */3106 3107 3108 /**3109 * Strip trailing blanks (space & tab).3110 *3111 * @returns True if modified, false if not.3112 * @param pIn The input stream.3113 * @param pOut The output stream.3114 * @param pSettings The settings.3115 */3116 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3117 {3118 if (!pSettings->fStripTrailingBlanks)3119 return false;3120 3121 bool fModified = false;3122 SCMEOL enmEol;3123 size_t cchLine;3124 const char *pchLine;3125 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3126 {3127 int rc;3128 if ( cchLine == 03129 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )3130 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3131 else3132 {3133 cchLine--;3134 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))3135 cchLine--;3136 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3137 fModified = true;3138 }3139 if (RT_FAILURE(rc))3140 return false;3141 }3142 if (fModified)3143 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");3144 return fModified;3145 }3146 3147 /**3148 * Expand tabs.3149 *3150 * @returns True if modified, false if not.3151 * @param pIn The input stream.3152 * @param pOut The output stream.3153 * @param pSettings The settings.3154 */3155 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3156 {3157 if (!pSettings->fConvertTabs)3158 return false;3159 3160 size_t const cchTab = pSettings->cchTab;3161 bool fModified = false;3162 SCMEOL enmEol;3163 size_t cchLine;3164 const char *pchLine;3165 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3166 {3167 int rc;3168 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);3169 if (!pchTab)3170 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3171 else3172 {3173 size_t offTab = 0;3174 const char *pchChunk = pchLine;3175 for (;;)3176 {3177 size_t cchChunk = pchTab - pchChunk;3178 offTab += cchChunk;3179 ScmStreamWrite(pOut, pchChunk, cchChunk);3180 3181 size_t cchToTab = cchTab - offTab % cchTab;3182 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);3183 offTab += cchToTab;3184 3185 pchChunk = pchTab + 1;3186 size_t cchLeft = cchLine - (pchChunk - pchLine);3187 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);3188 if (!pchTab)3189 {3190 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);3191 break;3192 }3193 }3194 3195 fModified = true;3196 }3197 if (RT_FAILURE(rc))3198 return false;3199 }3200 if (fModified)3201 ScmVerbose(pState, 2, " * Expanded tabs\n");3202 return fModified;3203 }3204 3205 /**3206 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.3207 *3208 * @returns true if modifications were made, false if not.3209 * @param pIn The input stream.3210 * @param pOut The output stream.3211 * @param pSettings The settings.3212 * @param enmDesiredEol The desired end of line indicator type.3213 * @param pszDesiredSvnEol The desired svn:eol-style.3214 */3215 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,3216 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)3217 {3218 if (!pSettings->fConvertEol)3219 return false;3220 3221 bool fModified = false;3222 SCMEOL enmEol;3223 size_t cchLine;3224 const char *pchLine;3225 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)3226 {3227 if ( enmEol != enmDesiredEol3228 && enmEol != SCMEOL_NONE)3229 {3230 fModified = true;3231 enmEol = enmDesiredEol;3232 }3233 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);3234 if (RT_FAILURE(rc))3235 return false;3236 }3237 if (fModified)3238 ScmVerbose(pState, 2, " * Converted EOL markers\n");3239 3240 /* Check svn:eol-style if appropriate */3241 if ( pSettings->fSetSvnEol3242 && scmSvnIsInWorkingCopy(pState))3243 {3244 char *pszEol;3245 int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);3246 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))3247 || rc == VERR_NOT_FOUND)3248 {3249 if (rc == VERR_NOT_FOUND)3250 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);3251 else3252 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);3253 int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);3254 if (RT_FAILURE(rc2))3255 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */3256 }3257 if (RT_SUCCESS(rc))3258 RTStrFree(pszEol);3259 }3260 3261 /** @todo also check the subversion svn:eol-style state! */3262 return fModified;3263 }3264 3265 /**3266 * Force native end of line indicator.3267 *3268 * @returns true if modifications were made, false if not.3269 * @param pIn The input stream.3270 * @param pOut The output stream.3271 * @param pSettings The settings.3272 */3273 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3274 {3275 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)3276 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");3277 #else3278 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");3279 #endif3280 }3281 3282 /**3283 * Force the stream to use LF as the end of line indicator.3284 *3285 * @returns true if modifications were made, false if not.3286 * @param pIn The input stream.3287 * @param pOut The output stream.3288 * @param pSettings The settings.3289 */3290 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3291 {3292 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");3293 }3294 3295 /**3296 * Force the stream to use CRLF as the end of line indicator.3297 *3298 * @returns true if modifications were made, false if not.3299 * @param pIn The input stream.3300 * @param pOut The output stream.3301 * @param pSettings The settings.3302 */3303 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3304 {3305 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");3306 }3307 3308 /**3309 * Strip trailing blank lines and/or make sure there is exactly one blank line3310 * at the end of the file.3311 *3312 * @returns true if modifications were made, false if not.3313 * @param pIn The input stream.3314 * @param pOut The output stream.3315 * @param pSettings The settings.3316 *3317 * @remarks ASSUMES trailing white space has been removed already.3318 */3319 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3320 {3321 if ( !pSettings->fStripTrailingLines3322 && !pSettings->fForceTrailingLine3323 && !pSettings->fForceFinalEol)3324 return false;3325 3326 size_t const cLines = ScmStreamCountLines(pIn);3327 3328 /* Empty files remains empty. */3329 if (cLines <= 1)3330 return false;3331 3332 /* Figure out if we need to adjust the number of lines or not. */3333 size_t cLinesNew = cLines;3334 3335 if ( pSettings->fStripTrailingLines3336 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))3337 {3338 while ( cLinesNew > 13339 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))3340 cLinesNew--;3341 }3342 3343 if ( pSettings->fForceTrailingLine3344 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))3345 cLinesNew++;3346 3347 bool fFixMissingEol = pSettings->fForceFinalEol3348 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;3349 3350 if ( !fFixMissingEol3351 && cLines == cLinesNew)3352 return false;3353 3354 /* Copy the number of lines we've arrived at. */3355 ScmStreamRewindForReading(pIn);3356 3357 size_t cCopied = RT_MIN(cLinesNew, cLines);3358 ScmStreamCopyLines(pOut, pIn, cCopied);3359 3360 if (cCopied != cLinesNew)3361 {3362 while (cCopied++ < cLinesNew)3363 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));3364 }3365 /* Fix missing EOL if required. */3366 else if (fFixMissingEol)3367 {3368 if (ScmStreamGetEol(pIn) == SCMEOL_LF)3369 ScmStreamWrite(pOut, "\n", 1);3370 else3371 ScmStreamWrite(pOut, "\r\n", 2);3372 }3373 3374 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");3375 return true;3376 }3377 3378 /**3379 * Make sure there is no svn:executable keyword on the current file.3380 *3381 * @returns false - the state carries these kinds of changes.3382 * @param pState The rewriter state.3383 * @param pIn The input stream.3384 * @param pOut The output stream.3385 * @param pSettings The settings.3386 */3387 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3388 {3389 if ( !pSettings->fSetSvnExecutable3390 || !scmSvnIsInWorkingCopy(pState))3391 return false;3392 3393 int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);3394 if (RT_SUCCESS(rc))3395 {3396 ScmVerbose(pState, 2, " * removing svn:executable\n");3397 rc = scmSvnDelProperty(pState, "svn:executable");3398 if (RT_FAILURE(rc))3399 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3400 }3401 return false;3402 }3403 3404 /**3405 * Make sure the Id and Revision keywords are expanded.3406 *3407 * @returns false - the state carries these kinds of changes.3408 * @param pState The rewriter state.3409 * @param pIn The input stream.3410 * @param pOut The output stream.3411 * @param pSettings The settings.3412 */3413 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3414 {3415 if ( !pSettings->fSetSvnKeywords3416 || !scmSvnIsInWorkingCopy(pState))3417 return false;3418 3419 char *pszKeywords;3420 int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);3421 if ( RT_SUCCESS(rc)3422 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */3423 || !strstr(pszKeywords, "Revision")) )3424 {3425 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))3426 rc = RTStrAAppend(&pszKeywords, " Id Revision");3427 else if (!strstr(pszKeywords, "Id"))3428 rc = RTStrAAppend(&pszKeywords, " Id");3429 else3430 rc = RTStrAAppend(&pszKeywords, " Revision");3431 if (RT_SUCCESS(rc))3432 {3433 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);3434 rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);3435 if (RT_FAILURE(rc))3436 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3437 }3438 else3439 RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */3440 RTStrFree(pszKeywords);3441 }3442 else if (rc == VERR_NOT_FOUND)3443 {3444 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");3445 rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");3446 if (RT_FAILURE(rc))3447 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */3448 }3449 else if (RT_SUCCESS(rc))3450 RTStrFree(pszKeywords);3451 3452 return false;3453 }3454 3455 /**3456 * Makefile.kup are empty files, enforce this.3457 *3458 * @returns true if modifications were made, false if not.3459 * @param pIn The input stream.3460 * @param pOut The output stream.3461 * @param pSettings The settings.3462 */3463 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3464 {3465 /* These files should be zero bytes. */3466 if (pIn->cb == 0)3467 return false;3468 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");3469 return true;3470 }3471 3472 /**3473 * Rewrite a kBuild makefile.3474 *3475 * @returns true if modifications were made, false if not.3476 * @param pIn The input stream.3477 * @param pOut The output stream.3478 * @param pSettings The settings.3479 *3480 * @todo3481 *3482 * Ideas for Makefile.kmk and Config.kmk:3483 * - sort if1of/ifn1of sets.3484 * - line continuation slashes should only be preceded by one space.3485 */3486 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3487 {3488 return false;3489 }3490 3491 /**3492 * Rewrite a C/C++ source or header file.3493 *3494 * @returns true if modifications were made, false if not.3495 * @param pIn The input stream.3496 * @param pOut The output stream.3497 * @param pSettings The settings.3498 *3499 * @todo3500 *3501 * Ideas for C/C++:3502 * - space after if, while, for, switch3503 * - spaces in for (i=0;i<x;i++)3504 * - complex conditional, bird style.3505 * - remove unnecessary parentheses.3506 * - sort defined RT_OS_*|| and RT_ARCH3507 * - sizeof without parenthesis.3508 * - defined without parenthesis.3509 * - trailing spaces.3510 * - parameter indentation.3511 * - space after comma.3512 * - while (x--); -> multi line + comment.3513 * - else statement;3514 * - space between function and left parenthesis.3515 * - TODO, XXX, @todo cleanup.3516 * - Space before/after '*'.3517 * - ensure new line at end of file.3518 * - Indentation of precompiler statements (#ifdef, #defines).3519 * - space between functions.3520 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.3521 */3522 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)3523 {3524 3525 return false;3526 }3527 3528 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */3529 3530 /**3531 * Processes a file.3532 *3533 * @returns IPRT status code.3534 * @param pState The rewriter state.3535 * @param pszFilename The file name.3536 * @param pszBasename The base name (pointer within @a pszFilename).3537 * @param cchBasename The length of the base name. (For passing to3538 * RTStrSimplePatternMultiMatch.)3539 * @param pBaseSettings The base settings to use. It's OK to modify3540 * these.3541 */3542 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,3543 PSCMSETTINGSBASE pBaseSettings)3544 {3545 /*3546 * Do the file level filtering.3547 */3548 if ( pBaseSettings->pszFilterFiles3549 && *pBaseSettings->pszFilterFiles3550 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))3551 {3552 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);3553 return VINF_SUCCESS;3554 }3555 if ( pBaseSettings->pszFilterOutFiles3556 && *pBaseSettings->pszFilterOutFiles3557 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)3558 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )3559 {3560 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);3561 return VINF_SUCCESS;3562 }3563 if ( pBaseSettings->fOnlySvnFiles3564 && !scmSvnIsInWorkingCopy(pState))3565 {3566 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);3567 return VINF_SUCCESS;3568 }3569 3570 /*3571 * Try find a matching rewrite config for this filename.3572 */3573 PCSCMCFGENTRY pCfg = NULL;3574 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)3575 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))3576 {3577 pCfg = &g_aConfigs[iCfg];3578 break;3579 }3580 if (!pCfg)3581 {3582 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);3583 return VINF_SUCCESS;3584 }3585 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);3586 3587 /*3588 * Create an input stream from the file and check that it's text.3589 */3590 SCMSTREAM Stream1;3591 int rc = ScmStreamInitForReading(&Stream1, pszFilename);3592 if (RT_FAILURE(rc))3593 {3594 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);3595 return rc;3596 }3597 if (ScmStreamIsText(&Stream1))3598 {3599 ScmVerbose(pState, 3, NULL);3600 3601 /*3602 * Gather SCM and editor settings from the stream.3603 */3604 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);3605 if (RT_SUCCESS(rc))3606 {3607 ScmStreamRewindForReading(&Stream1);3608 3609 /*3610 * Create two more streams for output and push the text thru all the3611 * rewriters, switching the two streams around when something is3612 * actually rewritten. Stream1 remains unchanged.3613 */3614 SCMSTREAM Stream2;3615 rc = ScmStreamInitForWriting(&Stream2, &Stream1);3616 if (RT_SUCCESS(rc))3617 {3618 SCMSTREAM Stream3;3619 rc = ScmStreamInitForWriting(&Stream3, &Stream1);3620 if (RT_SUCCESS(rc))3621 {3622 bool fModified = false;3623 PSCMSTREAM pIn = &Stream1;3624 PSCMSTREAM pOut = &Stream2;3625 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)3626 {3627 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);3628 if (fRc)3629 {3630 PSCMSTREAM pTmp = pOut;3631 pOut = pIn == &Stream1 ? &Stream3 : pIn;3632 pIn = pTmp;3633 fModified = true;3634 }3635 ScmStreamRewindForReading(pIn);3636 ScmStreamRewindForWriting(pOut);3637 }3638 3639 rc = ScmStreamGetStatus(&Stream1);3640 if (RT_SUCCESS(rc))3641 rc = ScmStreamGetStatus(&Stream2);3642 if (RT_SUCCESS(rc))3643 rc = ScmStreamGetStatus(&Stream3);3644 if (RT_SUCCESS(rc))3645 {3646 /*3647 * If rewritten, write it back to disk.3648 */3649 if (fModified)3650 {3651 if (!g_fDryRun)3652 {3653 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);3654 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);3655 if (RT_FAILURE(rc))3656 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);3657 }3658 else3659 {3660 ScmVerbose(pState, 1, NULL);3661 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,3662 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);3663 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);3664 }3665 }3666 3667 /*3668 * If pending SVN property changes, apply them.3669 */3670 if (pState->cSvnPropChanges && RT_SUCCESS(rc))3671 {3672 if (!g_fDryRun)3673 {3674 rc = scmSvnApplyChanges(pState);3675 if (RT_FAILURE(rc))3676 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);3677 }3678 else3679 scmSvnDisplayChanges(pState);3680 }3681 3682 if (!fModified && !pState->cSvnPropChanges)3683 ScmVerbose(pState, 3, "no change\n", pszFilename);3684 }3685 else3686 RTMsgError("%s: stream error %Rrc\n", pszFilename);3687 ScmStreamDelete(&Stream3);3688 }3689 else3690 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);3691 ScmStreamDelete(&Stream2);3692 }3693 else3694 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);3695 }3696 else3697 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);3698 }3699 else3700 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);3701 ScmStreamDelete(&Stream1);3702 3703 return rc;3704 }3705 3706 /**3707 * Processes a file.3708 *3709 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the3710 * directory recursion method.3711 *3712 * @returns IPRT status code.3713 * @param pszFilename The file name.3714 * @param pszBasename The base name (pointer within @a pszFilename).3715 * @param cchBasename The length of the base name. (For passing to3716 * RTStrSimplePatternMultiMatch.)3717 * @param pSettingsStack The settings stack (pointer to the top element).3718 */3719 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,3720 PSCMSETTINGS pSettingsStack)3721 {3722 SCMSETTINGSBASE Base;3723 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);3724 if (RT_SUCCESS(rc))3725 {3726 SCMRWSTATE State;3727 State.fFirst = false;3728 State.pszFilename = pszFilename;3729 State.cSvnPropChanges = 0;3730 State.paSvnPropChanges = NULL;3731 3732 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);3733 3734 size_t i = State.cSvnPropChanges;3735 while (i-- > 0)3736 {3737 RTStrFree(State.paSvnPropChanges[i].pszName);3738 RTStrFree(State.paSvnPropChanges[i].pszValue);3739 }3740 RTMemFree(State.paSvnPropChanges);3741 3742 scmSettingsBaseDelete(&Base);3743 }3744 return rc;3745 }3746 3747 3748 /**3749 * Tries to correct RTDIRENTRY_UNKNOWN.3750 *3751 * @returns Corrected type.3752 * @param pszPath The path to the object in question.3753 */3754 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)3755 {3756 RTFSOBJINFO Info;3757 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);3758 if (RT_FAILURE(rc))3759 return RTDIRENTRYTYPE_UNKNOWN;3760 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))3761 return RTDIRENTRYTYPE_DIRECTORY;3762 if (RTFS_IS_FILE(Info.Attr.fMode))3763 return RTDIRENTRYTYPE_FILE;3764 return RTDIRENTRYTYPE_UNKNOWN;3765 }3766 3767 /**3768 * Recurse into a sub-directory and process all the files and directories.3769 *3770 * @returns IPRT status code.3771 * @param pszBuf Path buffer containing the directory path on3772 * entry. This ends with a dot. This is passed3773 * along when recursing in order to save stack space3774 * and avoid needless copying.3775 * @param cchDir Length of our path in pszbuf.3776 * @param pEntry Directory entry buffer. This is also passed3777 * along when recursing to save stack space.3778 * @param pSettingsStack The settings stack (pointer to the top element).3779 * @param iRecursion The recursion depth. This is used to restrict3780 * the recursions.3781 */3782 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,3783 PSCMSETTINGS pSettingsStack, unsigned iRecursion)3784 {3785 int rc;3786 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');3787 3788 /*3789 * Make sure we stop somewhere.3790 */3791 if (iRecursion > 128)3792 {3793 RTMsgError("recursion too deep: %d\n", iRecursion);3794 return VINF_SUCCESS; /* ignore */3795 }3796 3797 /*3798 * Check if it's excluded by --only-svn-dir.3799 */3800 if (pSettingsStack->Base.fOnlySvnDirs)3801 {3802 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");3803 if (RT_FAILURE(rc))3804 {3805 RTMsgError("RTPathAppend: %Rrc\n", rc);3806 return rc;3807 }3808 if (!RTDirExists(pszBuf))3809 return VINF_SUCCESS;3810 3811 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));3812 pszBuf[cchDir] = '\0';3813 pszBuf[cchDir - 1] = '.';3814 }3815 3816 /*3817 * Try open and read the directory.3818 */3819 PRTDIR pDir;3820 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);3821 if (RT_FAILURE(rc))3822 {3823 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);3824 return rc;3825 }3826 for (;;)3827 {3828 /* Read the next entry. */3829 rc = RTDirRead(pDir, pEntry, NULL);3830 if (RT_FAILURE(rc))3831 {3832 if (rc == VERR_NO_MORE_FILES)3833 rc = VINF_SUCCESS;3834 else3835 RTMsgError("RTDirRead -> %Rrc\n", rc);3836 break;3837 }3838 3839 /* Skip '.' and '..'. */3840 if ( pEntry->szName[0] == '.'3841 && ( pEntry->cbName == 13842 || ( pEntry->cbName == 23843 && pEntry->szName[1] == '.')))3844 continue;3845 3846 /* Enter it into the buffer so we've got a full name to work3847 with when needed. */3848 if (pEntry->cbName + cchDir >= RTPATH_MAX)3849 {3850 RTMsgError("Skipping too long entry: %s", pEntry->szName);3851 continue;3852 }3853 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);3854 3855 /* Figure the type. */3856 RTDIRENTRYTYPE enmType = pEntry->enmType;3857 if (enmType == RTDIRENTRYTYPE_UNKNOWN)3858 enmType = scmFigureUnknownType(pszBuf);3859 3860 /* Process the file or directory, skip the rest. */3861 if (enmType == RTDIRENTRYTYPE_FILE)3862 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);3863 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)3864 {3865 /* Append the dot for the benefit of the pattern matching. */3866 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)3867 {3868 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);3869 continue;3870 }3871 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));3872 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;3873 3874 if ( !pSettingsStack->Base.pszFilterOutDirs3875 || !*pSettingsStack->Base.pszFilterOutDirs3876 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,3877 pEntry->szName, pEntry->cbName, NULL)3878 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,3879 pszBuf, cchSubDir, NULL)3880 )3881 )3882 {3883 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);3884 if (RT_SUCCESS(rc))3885 {3886 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);3887 scmSettingsStackPopAndDestroy(&pSettingsStack);3888 }3889 }3890 }3891 if (RT_FAILURE(rc))3892 break;3893 }3894 RTDirClose(pDir);3895 return rc;3896 3897 }3898 3899 /**3900 * Process a directory tree.3901 *3902 * @returns IPRT status code.3903 * @param pszDir The directory to start with. This is pointer to3904 * a RTPATH_MAX sized buffer.3905 */3906 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)3907 {3908 /*3909 * Setup the recursion.3910 */3911 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");3912 if (RT_SUCCESS(rc))3913 {3914 RTDIRENTRY Entry;3915 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);3916 }3917 else3918 RTMsgError("RTPathAppend: %Rrc\n", rc);3919 return rc;3920 }3921 3922 3923 /**3924 * Processes a file or directory specified as an command line argument.3925 *3926 * @returns IPRT status code3927 * @param pszSomething What we found in the command line arguments.3928 * @param pSettingsStack The settings stack (pointer to the top element).3929 */3930 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)3931 {3932 char szBuf[RTPATH_MAX];3933 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));3934 if (RT_SUCCESS(rc))3935 {3936 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);3937 3938 PSCMSETTINGS pSettings;3939 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);3940 if (RT_SUCCESS(rc))3941 {3942 scmSettingsStackPush(&pSettingsStack, pSettings);3943 3944 if (RTFileExists(szBuf))3945 {3946 const char *pszBasename = RTPathFilename(szBuf);3947 if (pszBasename)3948 {3949 size_t cchBasename = strlen(pszBasename);3950 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);3951 }3952 else3953 {3954 RTMsgError("RTPathFilename: NULL\n");3955 rc = VERR_IS_A_DIRECTORY;3956 }3957 }3958 else3959 rc = scmProcessDirTree(szBuf, pSettingsStack);3960 3961 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);3962 Assert(pPopped == pSettings);3963 scmSettingsDestroy(pSettings);3964 }3965 else3966 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);3967 }3968 else3969 RTMsgError("RTPathAbs: %Rrc\n", rc);3970 return rc;3971 }3972 3973 int main(int argc, char **argv)3974 {3975 int rc = RTR3InitExe(argc, &argv, 0);3976 if (RT_FAILURE(rc))3977 return 1;3978 3979 /*3980 * Init the settings.3981 */3982 PSCMSETTINGS pSettings;3983 rc = scmSettingsCreate(&pSettings, &g_Defaults);3984 if (RT_FAILURE(rc))3985 {3986 RTMsgError("scmSettingsCreate: %Rrc\n", rc);3987 return 1;3988 }3989 3990 /*3991 * Parse arguments and process input in order (because this is the only3992 * thing that works at the moment).3993 */3994 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =3995 {3996 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },3997 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },3998 { "--file-filter", 'f', RTGETOPT_REQ_STRING },3999 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },4000 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },4001 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },4002 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },4003 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },4004 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },4005 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },4006 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },4007 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },4008 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },4009 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },4010 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },4011 };4012 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));4013 4014 RTGETOPTUNION ValueUnion;4015 RTGETOPTSTATE GetOptState;4016 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);4017 AssertReleaseRCReturn(rc, 1);4018 size_t cProcessed = 0;4019 4020 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)4021 {4022 switch (rc)4023 {4024 case 'd':4025 g_fDryRun = true;4026 break;4027 case 'D':4028 g_fDryRun = false;4029 break;4030 4031 case 'f':4032 g_pszFileFilter = ValueUnion.psz;4033 break;4034 4035 case 'h':4036 RTPrintf("VirtualBox Source Code Massager\n"4037 "\n"4038 "Usage: %s [options] <files & dirs>\n"4039 "\n"4040 "Options:\n", g_szProgName);4041 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)4042 {4043 bool fAdvanceTwo = false;4044 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)4045 {4046 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)4047 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL4048 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL4049 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL4050 );4051 if (fAdvanceTwo)4052 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);4053 else4054 RTPrintf(" %s\n", s_aOpts[i].pszLong);4055 }4056 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)4057 RTPrintf(" %s string\n", s_aOpts[i].pszLong);4058 else4059 RTPrintf(" %s value\n", s_aOpts[i].pszLong);4060 switch (s_aOpts[i].iShort)4061 {4062 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;4063 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;4064 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;4065 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;4066 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;4067 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;4068 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;4069 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;4070 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;4071 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;4072 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;4073 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;4074 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;4075 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;4076 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;4077 }4078 i += fAdvanceTwo;4079 }4080 return 1;4081 4082 case 'q':4083 g_iVerbosity = 0;4084 break;4085 4086 case 'v':4087 g_iVerbosity++;4088 break;4089 4090 case 'V':4091 {4092 /* The following is assuming that svn does it's job here. */4093 static const char s_szRev[] = "$Revision$";4094 const char *psz = RTStrStripL(strchr(s_szRev, ' '));4095 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);4096 return 0;4097 }4098 4099 case SCMOPT_DIFF_IGNORE_EOL:4100 g_fDiffIgnoreEol = true;4101 break;4102 case SCMOPT_DIFF_NO_IGNORE_EOL:4103 g_fDiffIgnoreEol = false;4104 break;4105 4106 case SCMOPT_DIFF_IGNORE_SPACE:4107 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;4108 break;4109 case SCMOPT_DIFF_NO_IGNORE_SPACE:4110 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;4111 break;4112 4113 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:4114 g_fDiffIgnoreLeadingWS = true;4115 break;4116 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:4117 g_fDiffIgnoreLeadingWS = false;4118 break;4119 4120 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:4121 g_fDiffIgnoreTrailingWS = true;4122 break;4123 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:4124 g_fDiffIgnoreTrailingWS = false;4125 break;4126 4127 case SCMOPT_DIFF_SPECIAL_CHARS:4128 g_fDiffSpecialChars = true;4129 break;4130 case SCMOPT_DIFF_NO_SPECIAL_CHARS:4131 g_fDiffSpecialChars = false;4132 break;4133 4134 case VINF_GETOPT_NOT_OPTION:4135 {4136 if (!g_fDryRun)4137 {4138 if (!cProcessed)4139 {4140 RTPrintf("%s: Warning! This program will make changes to your source files and\n"4141 "%s: there is a slight risk that bugs or a full disk may cause\n"4142 "%s: LOSS OF DATA. So, please make sure you have checked in\n"4143 "%s: all your changes already. If you didn't, then don't blame\n"4144 "%s: anyone for not warning you!\n"4145 "%s:\n"4146 "%s: Press any key to continue...\n",4147 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,4148 g_szProgName, g_szProgName);4149 RTStrmGetCh(g_pStdIn);4150 }4151 cProcessed++;4152 }4153 rc = scmProcessSomething(ValueUnion.psz, pSettings);4154 if (RT_FAILURE(rc))4155 return rc;4156 break;4157 }4158 4159 default:4160 {4161 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);4162 if (RT_SUCCESS(rc2))4163 break;4164 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)4165 return 2;4166 return RTGetOptPrintError(rc, &ValueUnion);4167 }4168 }4169 }4170 4171 scmSettingsDestroy(pSettings);4172 return 0;4173 }4174
Note:
See TracChangeset
for help on using the changeset viewer.