/** * Copies @a cLines from the @a pSrc stream onto the @a pDst stream. * * The stream positions will be used and changed in both streams. * * @returns IPRT status code. * @param pDst The destination stream. Must be in write mode. * @param cLines The number of lines. (0 is accepted.) * @param pSrc The source stream. Must be in read mode. */ int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines) { AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED); if (RT_FAILURE(pDst->rc)) return pDst->rc; AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED); if (RT_FAILURE(pSrc->rc)) return pSrc->rc; while (cLines-- > 0) { SCMEOL enmEol; size_t cchLine; const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol); if (!pchLine) return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF); int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol); if (RT_FAILURE(rc)) return rc; } return VINF_SUCCESS; }
/** * Strip trailing blanks (space & tab). * * @returns True if modified, false if not. * @param pIn The input stream. * @param pOut The output stream. * @param pSettings The settings. */ bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) { if (!pSettings->fStripTrailingBlanks) return false; bool fModified = false; SCMEOL enmEol; size_t cchLine; const char *pchLine; while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) { int rc; if ( cchLine == 0 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) ) rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); else { cchLine--; while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1])) cchLine--; rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); fModified = true; } if (RT_FAILURE(rc)) return false; } if (fModified) ScmVerbose(pState, 2, " * Stripped trailing blanks\n"); return fModified; }
/** * Expand tabs. * * @returns True if modified, false if not. * @param pIn The input stream. * @param pOut The output stream. * @param pSettings The settings. */ bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) { if (!pSettings->fConvertTabs) return false; size_t const cchTab = pSettings->cchTab; bool fModified = false; SCMEOL enmEol; size_t cchLine; const char *pchLine; while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) { int rc; const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine); if (!pchTab) rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); else { size_t offTab = 0; const char *pchChunk = pchLine; for (;;) { size_t cchChunk = pchTab - pchChunk; offTab += cchChunk; ScmStreamWrite(pOut, pchChunk, cchChunk); size_t cchToTab = cchTab - offTab % cchTab; ScmStreamWrite(pOut, g_szTabSpaces, cchToTab); offTab += cchToTab; pchChunk = pchTab + 1; size_t cchLeft = cchLine - (pchChunk - pchLine); pchTab = (const char *)memchr(pchChunk, '\t', cchLeft); if (!pchTab) { rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol); break; } } fModified = true; } if (RT_FAILURE(rc)) return false; } if (fModified) ScmVerbose(pState, 2, " * Expanded tabs\n"); return fModified; }
/** * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF. * * @returns true if modifications were made, false if not. * @param pIn The input stream. * @param pOut The output stream. * @param pSettings The settings. * @param enmDesiredEol The desired end of line indicator type. * @param pszDesiredSvnEol The desired svn:eol-style. */ static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings, SCMEOL enmDesiredEol, const char *pszDesiredSvnEol) { if (!pSettings->fConvertEol) return false; bool fModified = false; SCMEOL enmEol; size_t cchLine; const char *pchLine; while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL) { if ( enmEol != enmDesiredEol && enmEol != SCMEOL_NONE) { fModified = true; enmEol = enmDesiredEol; } int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol); if (RT_FAILURE(rc)) return false; } if (fModified) ScmVerbose(pState, 2, " * Converted EOL markers\n"); /* Check svn:eol-style if appropriate */ if ( pSettings->fSetSvnEol && ScmSvnIsInWorkingCopy(pState)) { char *pszEol; int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol); if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol)) || rc == VERR_NOT_FOUND) { if (rc == VERR_NOT_FOUND) ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol); else ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol); int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol); if (RT_FAILURE(rc2)) RTMsgError("ScmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */ } if (RT_SUCCESS(rc)) RTStrFree(pszEol); } /** @todo also check the subversion svn:eol-style state! */ return fModified; }
/** * Queries the value of an SVN property. * * This will automatically adjust for scheduled changes. * * @returns IPRT status code. * @retval VERR_INVALID_STATE if not a SVN WC file. * @retval VERR_NOT_FOUND if the property wasn't found. * @param pState The rewrite state to work on. * @param pszName The property name. * @param ppszValue Where to return the property value. Free this * using RTStrFree. Optional. */ int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue) { /* * Look it up in the scheduled changes. */ size_t i = pState->cSvnPropChanges; while (i-- > 0) if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName)) { const char *pszValue = pState->paSvnPropChanges[i].pszValue; if (!pszValue) return VERR_NOT_FOUND; if (ppszValue) return RTStrDupEx(ppszValue, pszValue); return VINF_SUCCESS; } #ifdef SCM_WITHOUT_LIBSVN int rc; scmSvnFindSvnBinary(pState); if (g_enmSvnVersion < kScmSvnVersion_1_7) { /* * Hack: Read the .svn/props/<file>.svn-work file exists. */ char szPath[RTPATH_MAX]; rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath); if (RT_SUCCESS(rc) && !RTFileExists(szPath)) rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath); if (RT_SUCCESS(rc)) { SCMSTREAM Stream; rc = ScmStreamInitForReading(&Stream, szPath); if (RT_SUCCESS(rc)) { /* * The current format is K len\n<name>\nV len\n<value>\n" ... END. */ rc = VERR_NOT_FOUND; size_t const cchName = strlen(pszName); SCMEOL enmEol; size_t cchLine; const char *pchLine; while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL) { /* * Parse the 'K num' / 'END' line. */ if ( cchLine == 3 && !memcmp(pchLine, "END", 3)) break; size_t cchKey; if ( cchLine < 3 || pchLine[0] != 'K' || pchLine[1] != ' ' || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey) || cchKey == 0 || cchKey > 4096) { RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine); rc = VERR_PARSE_ERROR; break; } /* * Match the key and skip to the value line. Don't bother with * names containing EOL markers. */ size_t const offKey = ScmStreamTell(&Stream); bool fMatch = cchName == cchKey; if (fMatch) { pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol); if (!pchLine) break; fMatch = cchLine == cchName && !memcmp(pchLine, pszName, cchName); } if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey))) break; if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1))) break; /* * Read and Parse the 'V num' line. */ pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol); if (!pchLine) break; size_t cchValue; if ( cchLine < 3 || pchLine[0] != 'V' || pchLine[1] != ' ' || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue) || cchValue > _1M) { RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine); rc = VERR_PARSE_ERROR; break; } /* * If we have a match, allocate a return buffer and read the * value into it. Otherwise skip this value and continue * searching. */ if (fMatch) { if (!ppszValue) rc = VINF_SUCCESS; else { char *pszValue; rc = RTStrAllocEx(&pszValue, cchValue + 1); if (RT_SUCCESS(rc)) { rc = ScmStreamRead(&Stream, pszValue, cchValue); if (RT_SUCCESS(rc)) *ppszValue = pszValue; else RTStrFree(pszValue); } } break; } if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue))) break; if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1))) break; } if (RT_FAILURE(ScmStreamGetStatus(&Stream))) { rc = ScmStreamGetStatus(&Stream); RTMsgError("%s: stream error %Rrc\n", szPath, rc); } ScmStreamDelete(&Stream); } } if (rc == VERR_FILE_NOT_FOUND) rc = VERR_NOT_FOUND; } else { const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL }; char *pszValue; rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue); if (RT_SUCCESS(rc)) { if (pszValue && *pszValue) { if (ppszValue) { *ppszValue = pszValue; pszValue = NULL; } } else rc = VERR_NOT_FOUND; RTStrFree(pszValue); } } return rc; #else NOREF(pState); #endif return VERR_NOT_FOUND; }
/** * Creates a diff of the changes between the streams @a pLeft and @a pRight. * * This currently only implements the simplest diff format, so no contexts. * * Also, note that we won't detect differences in the final newline of the * streams. * * @returns The number of differences. * @param pszFilename The filename. * @param pLeft The left side stream. * @param pRight The right side stream. * @param fIgnoreEol Whether to ignore end of line markers. * @param fIgnoreLeadingWhite Set if leading white space should be ignored. * @param fIgnoreTrailingWhite Set if trailing white space should be ignored. * @param fSpecialChars Whether to print special chars in a human * readable form or not. * @param cchTab The tab size. * @param pDiff Where to write the diff. */ size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol, bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars, size_t cchTab, PRTSTREAM pDiff) { #ifdef RT_STRICT ScmStreamCheckItegrity(pLeft); ScmStreamCheckItegrity(pRight); #endif /* * Set up the diff state. */ SCMDIFFSTATE State; State.cDiffs = 0; State.pszFilename = pszFilename; State.pLeft = pLeft; State.pRight = pRight; State.fIgnoreEol = fIgnoreEol; State.fIgnoreLeadingWhite = fIgnoreLeadingWhite; State.fIgnoreTrailingWhite = fIgnoreTrailingWhite; State.fSpecialChars = fSpecialChars; State.cchTab = cchTab; State.pDiff = pDiff; /* * Compare them line by line. */ ScmStreamRewindForReading(pLeft); ScmStreamRewindForReading(pRight); const char *pchLeft; const char *pchRight; for (;;) { SCMEOL enmEolLeft; size_t cchLeft; pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft); SCMEOL enmEolRight; size_t cchRight; pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight); if (!pchLeft || !pchRight) break; if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight)) scmDiffSynchronize(&State, 3); } /* * Deal with any remaining differences. */ if (pchLeft) scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0); else if (pchRight) scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0); /* * Report any errors. */ if (RT_FAILURE(ScmStreamGetStatus(pLeft))) RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft)); if (RT_FAILURE(ScmStreamGetStatus(pRight))) RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight)); return State.cDiffs; }