VirtualBox

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

Last change on this file since 48954 was 40530, checked in by vboxsync, 13 years ago

scm: Splitting out the diff code while I'm at it.

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