/** * 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; }
/** * 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; }
/** * Resynchronize the two streams and reports the difference. * * Upon return, the streams will be positioned after the block of @a cMatches * lines where it resynchronized them. * * @returns pState->cDiffs (just so we can use it in a return statement). * @param pState The state. * @param cMatches The number of lines that needs to match for the * stream to be considered synchronized again. */ static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches) { size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1; size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1; Assert(cMatches > 0); /* * Compare each new line from each of the streams will all the preceding * ones, including iStartLeft/Right. */ for (size_t iRange = 1; ; iRange++) { /* * Get the next line in the left stream and compare it against all the * preceding lines on the right side. */ SCMEOL enmEol; size_t cchLine; const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol); if (!pchLine) return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0); for (size_t iRight = cMatches - 1; iRight < iRange; iRight++) { SCMEOL enmEolRight; size_t cchRight; const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight, &cchRight, &enmEolRight); if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight) && scmDiffCompareLines(pState, iStartLeft + iRange + 1 - cMatches, iStartRight + iRight + 1 - cMatches, cMatches - 1) ) return scmDiffReport(pState, cMatches, iStartLeft, iRange + 1 - cMatches, iStartRight, iRight + 1 - cMatches); } /* * Get the next line in the right stream and compare it against all the * lines on the right side. */ pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol); if (!pchLine) return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0); for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++) { SCMEOL enmEolLeft; size_t cchLeft; const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft, &cchLeft, &enmEolLeft); if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol) && scmDiffCompareLines(pState, iStartLeft + iLeft + 1 - cMatches, iStartRight + iRange + 1 - cMatches, cMatches - 1) ) return scmDiffReport(pState, cMatches, iStartLeft, iLeft + 1 - cMatches, iStartRight, iRange + 1 - cMatches); } } }