VirtualBox

source: vbox/trunk/src/bldprogs/scmdiff.cpp@ 93941

Last change on this file since 93941 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.0 KB
Line 
1/* $Id: scmdiff.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ctype.h>
24#include <iprt/message.h>
25#include <iprt/stream.h>
26#include <iprt/string.h>
27
28#include "scmdiff.h"
29
30
31/*********************************************************************************************************************************
32* Global Variables *
33*********************************************************************************************************************************/
34static const char g_szTabSpaces[16+1] = " ";
35
36
37
38/**
39 * Prints a range of lines with a prefix.
40 *
41 * @param pState The diff state.
42 * @param chPrefix The prefix.
43 * @param pStream The stream to get the lines from.
44 * @param iLine The first line.
45 * @param cLines The number of lines.
46 */
47static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
48{
49 while (cLines-- > 0)
50 {
51 SCMEOL enmEol;
52 size_t cchLine;
53 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
54
55 RTStrmPutCh(pState->pDiff, chPrefix);
56 if (pchLine && cchLine)
57 {
58 if (!pState->fSpecialChars)
59 RTStrmWrite(pState->pDiff, pchLine, cchLine);
60 else
61 {
62 size_t offVir = 0;
63 const char *pchStart = pchLine;
64 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
65 while (pchTab)
66 {
67 RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
68 offVir += pchTab - pchStart;
69
70 size_t cchTab = pState->cchTab - offVir % pState->cchTab;
71 switch (cchTab)
72 {
73 case 1: RTStrmPutStr(pState->pDiff, "."); break;
74 case 2: RTStrmPutStr(pState->pDiff, ".."); break;
75 case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
76 case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
77 case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
78 default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
79 }
80 offVir += cchTab;
81
82 /* next */
83 pchStart = pchTab + 1;
84 pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
85 }
86 size_t cchLeft = cchLine - (pchStart - pchLine);
87 if (cchLeft)
88 RTStrmWrite(pState->pDiff, pchStart, cchLeft);
89 }
90 }
91
92 if (!pState->fSpecialChars)
93 RTStrmPutCh(pState->pDiff, '\n');
94 else if (enmEol == SCMEOL_LF)
95 RTStrmPutStr(pState->pDiff, "[LF]\n");
96 else if (enmEol == SCMEOL_CRLF)
97 RTStrmPutStr(pState->pDiff, "[CRLF]\n");
98 else
99 RTStrmPutStr(pState->pDiff, "[NONE]\n");
100
101 iLine++;
102 }
103}
104
105
106/**
107 * Reports a difference and propels the streams to the lines following the
108 * resync.
109 *
110 *
111 * @returns New pState->cDiff value (just to return something).
112 * @param pState The diff state. The cDiffs member will be
113 * incremented.
114 * @param cMatches The resync length.
115 * @param iLeft Where the difference starts on the left side.
116 * @param cLeft How long it is on this side. ~(size_t)0 is used
117 * to indicate that it goes all the way to the end.
118 * @param iRight Where the difference starts on the right side.
119 * @param cRight How long it is.
120 */
121static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
122 size_t iLeft, size_t cLeft,
123 size_t iRight, size_t cRight)
124{
125 /*
126 * Adjust the input.
127 */
128 if (cLeft == ~(size_t)0)
129 {
130 size_t c = ScmStreamCountLines(pState->pLeft);
131 if (c >= iLeft)
132 cLeft = c - iLeft;
133 else
134 {
135 iLeft = c;
136 cLeft = 0;
137 }
138 }
139
140 if (cRight == ~(size_t)0)
141 {
142 size_t c = ScmStreamCountLines(pState->pRight);
143 if (c >= iRight)
144 cRight = c - iRight;
145 else
146 {
147 iRight = c;
148 cRight = 0;
149 }
150 }
151
152 /*
153 * Print header if it's the first difference
154 */
155 if (!pState->cDiffs)
156 RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
157
158 /*
159 * Emit the change description.
160 */
161 char ch = cLeft == 0
162 ? 'a'
163 : cRight == 0
164 ? 'd'
165 : 'c';
166 if (cLeft > 1 && cRight > 1)
167 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
168 else if (cLeft > 1)
169 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);
170 else if (cRight > 1)
171 RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);
172 else
173 RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);
174
175 /*
176 * And the lines.
177 */
178 if (cLeft)
179 scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
180 if (cLeft && cRight)
181 RTStrmPrintf(pState->pDiff, "---\n");
182 if (cRight)
183 scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
184
185 /*
186 * Reposition the streams (safely ignores return value).
187 */
188 ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);
189 ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
190
191 pState->cDiffs++;
192 return pState->cDiffs;
193}
194
195/**
196 * Helper for scmDiffCompare that takes care of trailing spaces and stuff
197 * like that.
198 */
199static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
200 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
201 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
202{
203 if (pState->fIgnoreTrailingWhite)
204 {
205 while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
206 cchLeft--;
207 while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
208 cchRight--;
209 }
210
211 if (pState->fIgnoreLeadingWhite)
212 {
213 while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
214 pchLeft++, cchLeft--;
215 while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
216 pchRight++, cchRight--;
217 }
218
219 if ( cchLeft != cchRight
220 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
221 || memcmp(pchLeft, pchRight, cchLeft))
222 return false;
223 return true;
224}
225
226/**
227 * Compare two lines.
228 *
229 * @returns true if the are equal, false if not.
230 */
231DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
232 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
233 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
234{
235 if ( cchLeft != cchRight
236 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
237 || memcmp(pchLeft, pchRight, cchLeft))
238 {
239 if ( pState->fIgnoreTrailingWhite
240 || pState->fIgnoreLeadingWhite)
241 return scmDiffCompareSlow(pState,
242 pchLeft, cchLeft, enmEolLeft,
243 pchRight, cchRight, enmEolRight);
244 return false;
245 }
246 return true;
247}
248
249/**
250 * Compares two sets of lines from the two files.
251 *
252 * @returns true if they matches, false if they don't.
253 * @param pState The diff state.
254 * @param iLeft Where to start in the left stream.
255 * @param iRight Where to start in the right stream.
256 * @param cLines How many lines to compare.
257 */
258static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
259{
260 for (size_t iLine = 0; iLine < cLines; iLine++)
261 {
262 SCMEOL enmEolLeft;
263 size_t cchLeft;
264 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);
265
266 SCMEOL enmEolRight;
267 size_t cchRight;
268 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
269
270 if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
271 return false;
272 }
273 return true;
274}
275
276
277/**
278 * Resynchronize the two streams and reports the difference.
279 *
280 * Upon return, the streams will be positioned after the block of @a cMatches
281 * lines where it resynchronized them.
282 *
283 * @returns pState->cDiffs (just so we can use it in a return statement).
284 * @param pState The state.
285 * @param cMatches The number of lines that needs to match for the
286 * stream to be considered synchronized again.
287 */
288static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
289{
290 size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;
291 size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
292 Assert(cMatches > 0);
293
294 /*
295 * Compare each new line from each of the streams will all the preceding
296 * ones, including iStartLeft/Right.
297 */
298 for (size_t iRange = 1; ; iRange++)
299 {
300 /*
301 * Get the next line in the left stream and compare it against all the
302 * preceding lines on the right side.
303 */
304 SCMEOL enmEol;
305 size_t cchLine;
306 const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
307 if (!pchLine)
308 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
309
310 for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
311 {
312 SCMEOL enmEolRight;
313 size_t cchRight;
314 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
315 &cchRight, &enmEolRight);
316 if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
317 && scmDiffCompareLines(pState,
318 iStartLeft + iRange + 1 - cMatches,
319 iStartRight + iRight + 1 - cMatches,
320 cMatches - 1)
321 )
322 return scmDiffReport(pState, cMatches,
323 iStartLeft, iRange + 1 - cMatches,
324 iStartRight, iRight + 1 - cMatches);
325 }
326
327 /*
328 * Get the next line in the right stream and compare it against all the
329 * lines on the right side.
330 */
331 pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
332 if (!pchLine)
333 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
334
335 for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
336 {
337 SCMEOL enmEolLeft;
338 size_t cchLeft;
339 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
340 &cchLeft, &enmEolLeft);
341 if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
342 && scmDiffCompareLines(pState,
343 iStartLeft + iLeft + 1 - cMatches,
344 iStartRight + iRange + 1 - cMatches,
345 cMatches - 1)
346 )
347 return scmDiffReport(pState, cMatches,
348 iStartLeft, iLeft + 1 - cMatches,
349 iStartRight, iRange + 1 - cMatches);
350 }
351 }
352}
353
354/**
355 * Creates a diff of the changes between the streams @a pLeft and @a pRight.
356 *
357 * This currently only implements the simplest diff format, so no contexts.
358 *
359 * Also, note that we won't detect differences in the final newline of the
360 * streams.
361 *
362 * @returns The number of differences.
363 * @param pszFilename The filename.
364 * @param pLeft The left side stream.
365 * @param pRight The right side stream.
366 * @param fIgnoreEol Whether to ignore end of line markers.
367 * @param fIgnoreLeadingWhite Set if leading white space should be ignored.
368 * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.
369 * @param fSpecialChars Whether to print special chars in a human
370 * readable form or not.
371 * @param cchTab The tab size.
372 * @param pDiff Where to write the diff.
373 */
374size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
375 bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
376 size_t cchTab, PRTSTREAM pDiff)
377{
378#ifdef RT_STRICT
379 ScmStreamCheckItegrity(pLeft);
380 ScmStreamCheckItegrity(pRight);
381#endif
382
383 /*
384 * Set up the diff state.
385 */
386 SCMDIFFSTATE State;
387 State.cDiffs = 0;
388 State.pszFilename = pszFilename;
389 State.pLeft = pLeft;
390 State.pRight = pRight;
391 State.fIgnoreEol = fIgnoreEol;
392 State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;
393 State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;
394 State.fSpecialChars = fSpecialChars;
395 State.cchTab = cchTab;
396 State.pDiff = pDiff;
397
398 /*
399 * Compare them line by line.
400 */
401 ScmStreamRewindForReading(pLeft);
402 ScmStreamRewindForReading(pRight);
403 const char *pchLeft;
404 const char *pchRight;
405
406 for (;;)
407 {
408 SCMEOL enmEolLeft;
409 size_t cchLeft;
410 pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);
411
412 SCMEOL enmEolRight;
413 size_t cchRight;
414 pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
415 if (!pchLeft || !pchRight)
416 break;
417
418 if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
419 scmDiffSynchronize(&State, 3);
420 }
421
422 /*
423 * Deal with any remaining differences.
424 */
425 if (pchLeft)
426 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
427 else if (pchRight)
428 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
429
430 /*
431 * Report any errors.
432 */
433 if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
434 RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
435 if (RT_FAILURE(ScmStreamGetStatus(pRight)))
436 RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
437
438 return State.cDiffs;
439}
440
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette