VirtualBox

source: vbox/trunk/src/bldprogs/scm.cpp@ 56324

Last change on this file since 56324 was 56324, checked in by vboxsync, 9 years ago

updates

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.1 KB
Line 
1/* $Id: scm.cpp 56324 2015-06-09 23:00:39Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2015 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/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>
36
37#include "scm.h"
38#include "scmdiff.h"
39
40
41/*******************************************************************************
42* Defined Constants And Macros *
43*******************************************************************************/
44/** The name of the settings files. */
45#define SCM_SETTINGS_FILENAME ".scm-settings"
46
47
48/*******************************************************************************
49* Structures and Typedefs *
50*******************************************************************************/
51
52/**
53 * Option identifiers.
54 *
55 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
56 * clear. So, the option setting a flag (boolean) will have an even
57 * number and the one clearing it will have an odd number.
58 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
59 */
60typedef enum SCMOPT
61{
62 SCMOPT_CONVERT_EOL = 10000,
63 SCMOPT_NO_CONVERT_EOL,
64 SCMOPT_CONVERT_TABS,
65 SCMOPT_NO_CONVERT_TABS,
66 SCMOPT_FORCE_FINAL_EOL,
67 SCMOPT_NO_FORCE_FINAL_EOL,
68 SCMOPT_FORCE_TRAILING_LINE,
69 SCMOPT_NO_FORCE_TRAILING_LINE,
70 SCMOPT_STRIP_TRAILING_BLANKS,
71 SCMOPT_NO_STRIP_TRAILING_BLANKS,
72 SCMOPT_STRIP_TRAILING_LINES,
73 SCMOPT_NO_STRIP_TRAILING_LINES,
74 SCMOPT_ONLY_SVN_DIRS,
75 SCMOPT_NOT_ONLY_SVN_DIRS,
76 SCMOPT_ONLY_SVN_FILES,
77 SCMOPT_NOT_ONLY_SVN_FILES,
78 SCMOPT_SET_SVN_EOL,
79 SCMOPT_DONT_SET_SVN_EOL,
80 SCMOPT_SET_SVN_EXECUTABLE,
81 SCMOPT_DONT_SET_SVN_EXECUTABLE,
82 SCMOPT_SET_SVN_KEYWORDS,
83 SCMOPT_DONT_SET_SVN_KEYWORDS,
84 SCMOPT_TAB_SIZE,
85 SCMOPT_FILTER_OUT_DIRS,
86 SCMOPT_FILTER_FILES,
87 SCMOPT_FILTER_OUT_FILES,
88 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
89 //
90 SCMOPT_DIFF_IGNORE_EOL,
91 SCMOPT_DIFF_NO_IGNORE_EOL,
92 SCMOPT_DIFF_IGNORE_SPACE,
93 SCMOPT_DIFF_NO_IGNORE_SPACE,
94 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
95 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
96 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
97 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
98 SCMOPT_DIFF_SPECIAL_CHARS,
99 SCMOPT_DIFF_NO_SPECIAL_CHARS,
100 SCMOPT_END
101} SCMOPT;
102
103
104/*******************************************************************************
105* Global Variables *
106*******************************************************************************/
107const char g_szTabSpaces[16+1] = " ";
108static const char g_szProgName[] = "scm";
109static const char *g_pszChangedSuff = "";
110static bool g_fDryRun = true;
111static bool g_fDiffSpecialChars = true;
112static bool g_fDiffIgnoreEol = false;
113static bool g_fDiffIgnoreLeadingWS = false;
114static bool g_fDiffIgnoreTrailingWS = false;
115static int g_iVerbosity = 2;//99; //0;
116
117/** The global settings. */
118static SCMSETTINGSBASE const g_Defaults =
119{
120 /* .fConvertEol = */ true,
121 /* .fConvertTabs = */ true,
122 /* .fForceFinalEol = */ true,
123 /* .fForceTrailingLine = */ false,
124 /* .fStripTrailingBlanks = */ true,
125 /* .fStripTrailingLines = */ true,
126 /* .fOnlySvnFiles = */ false,
127 /* .fOnlySvnDirs = */ false,
128 /* .fSetSvnEol = */ false,
129 /* .fSetSvnExecutable = */ false,
130 /* .fSetSvnKeywords = */ false,
131 /* .cchTab = */ 8,
132 /* .pszFilterFiles = */ (char *)"",
133 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
134 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
135};
136
137/** Option definitions for the base settings. */
138static RTGETOPTDEF g_aScmOpts[] =
139{
140 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
141 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
142 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
143 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
144 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
145 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
146 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
147 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
148 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
149 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
150 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
151 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
152 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
153 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
154 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
155 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
156 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
157 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
158 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
159 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
160 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
161 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
162 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
163 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
164 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
165 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
166};
167
168/** Consider files matching the following patterns (base names only). */
169static const char *g_pszFileFilter = NULL;
170
171static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
172{
173 rewrite_SvnNoExecutable,
174 rewrite_Makefile_kup
175};
176
177static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
178{
179 rewrite_ForceNativeEol,
180 rewrite_StripTrailingBlanks,
181 rewrite_AdjustTrailingLines,
182 rewrite_SvnNoExecutable,
183 rewrite_SvnKeywords,
184 rewrite_Makefile_kmk
185};
186
187static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
188{
189 rewrite_ForceNativeEol,
190 rewrite_ExpandTabs,
191 rewrite_StripTrailingBlanks,
192 rewrite_AdjustTrailingLines,
193 rewrite_SvnNoExecutable,
194 rewrite_SvnKeywords,
195 rewrite_C_and_CPP
196};
197
198static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
199{
200 rewrite_ForceNativeEol,
201 rewrite_ExpandTabs,
202 rewrite_StripTrailingBlanks,
203 rewrite_AdjustTrailingLines,
204 rewrite_SvnNoExecutable,
205 rewrite_C_and_CPP
206};
207
208static PFNSCMREWRITER const g_aRewritersFor_RC[] =
209{
210 rewrite_ForceNativeEol,
211 rewrite_ExpandTabs,
212 rewrite_StripTrailingBlanks,
213 rewrite_AdjustTrailingLines,
214 rewrite_SvnNoExecutable,
215 rewrite_SvnKeywords
216};
217
218static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
219{
220 rewrite_ForceLF,
221 rewrite_ExpandTabs,
222 rewrite_StripTrailingBlanks
223};
224
225static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
226{
227 rewrite_ForceCRLF,
228 rewrite_ExpandTabs,
229 rewrite_StripTrailingBlanks
230};
231
232static PFNSCMREWRITER const g_aRewritersFor_Python[] =
233{
234 /** @todo rewrite_ForceLFIfExecutable */
235 rewrite_ExpandTabs,
236 rewrite_StripTrailingBlanks,
237 rewrite_AdjustTrailingLines,
238 rewrite_SvnKeywords
239};
240
241
242static SCMCFGENTRY const g_aConfigs[] =
243{
244 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
245 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
246 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
247 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
248 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
249 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
250 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
251 { RT_ELEMENTS(g_aRewritersFor_Python), &g_aRewritersFor_Python[0], "*.py" },
252};
253
254
255
256/* -=-=-=-=-=- settings -=-=-=-=-=- */
257
258
259/**
260 * Init a settings structure with settings from @a pSrc.
261 *
262 * @returns IPRT status code
263 * @param pSettings The settings.
264 * @param pSrc The source settings.
265 */
266static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
267{
268 *pSettings = *pSrc;
269
270 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
271 if (RT_SUCCESS(rc))
272 {
273 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
274 if (RT_SUCCESS(rc))
275 {
276 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
277 if (RT_SUCCESS(rc))
278 return VINF_SUCCESS;
279
280 RTStrFree(pSettings->pszFilterOutFiles);
281 }
282 RTStrFree(pSettings->pszFilterFiles);
283 }
284
285 pSettings->pszFilterFiles = NULL;
286 pSettings->pszFilterOutFiles = NULL;
287 pSettings->pszFilterOutDirs = NULL;
288 return rc;
289}
290
291/**
292 * Init a settings structure.
293 *
294 * @returns IPRT status code
295 * @param pSettings The settings.
296 */
297static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
298{
299 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
300}
301
302/**
303 * Deletes the settings, i.e. free any dynamically allocated content.
304 *
305 * @param pSettings The settings.
306 */
307static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
308{
309 if (pSettings)
310 {
311 Assert(pSettings->cchTab != ~(unsigned)0);
312 pSettings->cchTab = ~(unsigned)0;
313
314 RTStrFree(pSettings->pszFilterFiles);
315 pSettings->pszFilterFiles = NULL;
316
317 RTStrFree(pSettings->pszFilterOutFiles);
318 pSettings->pszFilterOutFiles = NULL;
319
320 RTStrFree(pSettings->pszFilterOutDirs);
321 pSettings->pszFilterOutDirs = NULL;
322 }
323}
324
325
326/**
327 * Processes a RTGetOpt result.
328 *
329 * @retval VINF_SUCCESS if handled.
330 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
331 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
332 *
333 * @param pSettings The settings to change.
334 * @param rc The RTGetOpt return value.
335 * @param pValueUnion The RTGetOpt value union.
336 */
337static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
338{
339 switch (rc)
340 {
341 case SCMOPT_CONVERT_EOL:
342 pSettings->fConvertEol = true;
343 return VINF_SUCCESS;
344 case SCMOPT_NO_CONVERT_EOL:
345 pSettings->fConvertEol = false;
346 return VINF_SUCCESS;
347
348 case SCMOPT_CONVERT_TABS:
349 pSettings->fConvertTabs = true;
350 return VINF_SUCCESS;
351 case SCMOPT_NO_CONVERT_TABS:
352 pSettings->fConvertTabs = false;
353 return VINF_SUCCESS;
354
355 case SCMOPT_FORCE_FINAL_EOL:
356 pSettings->fForceFinalEol = true;
357 return VINF_SUCCESS;
358 case SCMOPT_NO_FORCE_FINAL_EOL:
359 pSettings->fForceFinalEol = false;
360 return VINF_SUCCESS;
361
362 case SCMOPT_FORCE_TRAILING_LINE:
363 pSettings->fForceTrailingLine = true;
364 return VINF_SUCCESS;
365 case SCMOPT_NO_FORCE_TRAILING_LINE:
366 pSettings->fForceTrailingLine = false;
367 return VINF_SUCCESS;
368
369 case SCMOPT_STRIP_TRAILING_BLANKS:
370 pSettings->fStripTrailingBlanks = true;
371 return VINF_SUCCESS;
372 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
373 pSettings->fStripTrailingBlanks = false;
374 return VINF_SUCCESS;
375
376 case SCMOPT_STRIP_TRAILING_LINES:
377 pSettings->fStripTrailingLines = true;
378 return VINF_SUCCESS;
379 case SCMOPT_NO_STRIP_TRAILING_LINES:
380 pSettings->fStripTrailingLines = false;
381 return VINF_SUCCESS;
382
383 case SCMOPT_ONLY_SVN_DIRS:
384 pSettings->fOnlySvnDirs = true;
385 return VINF_SUCCESS;
386 case SCMOPT_NOT_ONLY_SVN_DIRS:
387 pSettings->fOnlySvnDirs = false;
388 return VINF_SUCCESS;
389
390 case SCMOPT_ONLY_SVN_FILES:
391 pSettings->fOnlySvnFiles = true;
392 return VINF_SUCCESS;
393 case SCMOPT_NOT_ONLY_SVN_FILES:
394 pSettings->fOnlySvnFiles = false;
395 return VINF_SUCCESS;
396
397 case SCMOPT_SET_SVN_EOL:
398 pSettings->fSetSvnEol = true;
399 return VINF_SUCCESS;
400 case SCMOPT_DONT_SET_SVN_EOL:
401 pSettings->fSetSvnEol = false;
402 return VINF_SUCCESS;
403
404 case SCMOPT_SET_SVN_EXECUTABLE:
405 pSettings->fSetSvnExecutable = true;
406 return VINF_SUCCESS;
407 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
408 pSettings->fSetSvnExecutable = false;
409 return VINF_SUCCESS;
410
411 case SCMOPT_SET_SVN_KEYWORDS:
412 pSettings->fSetSvnKeywords = true;
413 return VINF_SUCCESS;
414 case SCMOPT_DONT_SET_SVN_KEYWORDS:
415 pSettings->fSetSvnKeywords = false;
416 return VINF_SUCCESS;
417
418 case SCMOPT_TAB_SIZE:
419 if ( pValueUnion->u8 < 1
420 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
421 {
422 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
423 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
424 return VERR_OUT_OF_RANGE;
425 }
426 pSettings->cchTab = pValueUnion->u8;
427 return VINF_SUCCESS;
428
429 case SCMOPT_FILTER_OUT_DIRS:
430 case SCMOPT_FILTER_FILES:
431 case SCMOPT_FILTER_OUT_FILES:
432 {
433 char **ppsz = NULL;
434 switch (rc)
435 {
436 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
437 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
438 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
439 }
440
441 /*
442 * An empty string zaps the current list.
443 */
444 if (!*pValueUnion->psz)
445 return RTStrATruncate(ppsz, 0);
446
447 /*
448 * Non-empty strings are appended to the pattern list.
449 *
450 * Strip leading and trailing pattern separators before attempting
451 * to append it. If it's just separators, don't do anything.
452 */
453 const char *pszSrc = pValueUnion->psz;
454 while (*pszSrc == '|')
455 pszSrc++;
456 size_t cchSrc = strlen(pszSrc);
457 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
458 cchSrc--;
459 if (!cchSrc)
460 return VINF_SUCCESS;
461
462 return RTStrAAppendExN(ppsz, 2,
463 "|", *ppsz && **ppsz ? (size_t)1 : (size_t)0,
464 pszSrc, cchSrc);
465 }
466
467 default:
468 return VERR_GETOPT_UNKNOWN_OPTION;
469 }
470}
471
472/**
473 * Parses an option string.
474 *
475 * @returns IPRT status code.
476 * @param pBase The base settings structure to apply the options
477 * to.
478 * @param pszOptions The options to parse.
479 */
480static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
481{
482 int cArgs;
483 char **papszArgs;
484 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
485 if (RT_SUCCESS(rc))
486 {
487 RTGETOPTUNION ValueUnion;
488 RTGETOPTSTATE GetOptState;
489 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
490 if (RT_SUCCESS(rc))
491 {
492 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
493 {
494 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
495 if (RT_FAILURE(rc))
496 break;
497 }
498 }
499 RTGetOptArgvFree(papszArgs);
500 }
501
502 return rc;
503}
504
505/**
506 * Parses an unterminated option string.
507 *
508 * @returns IPRT status code.
509 * @param pBase The base settings structure to apply the options
510 * to.
511 * @param pchLine The line.
512 * @param cchLine The line length.
513 */
514static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
515{
516 char *pszLine = RTStrDupN(pchLine, cchLine);
517 if (!pszLine)
518 return VERR_NO_MEMORY;
519 int rc = scmSettingsBaseParseString(pBase, pszLine);
520 RTStrFree(pszLine);
521 return rc;
522}
523
524/**
525 * Verifies the options string.
526 *
527 * @returns IPRT status code.
528 * @param pszOptions The options to verify .
529 */
530static int scmSettingsBaseVerifyString(const char *pszOptions)
531{
532 SCMSETTINGSBASE Base;
533 int rc = scmSettingsBaseInit(&Base);
534 if (RT_SUCCESS(rc))
535 {
536 rc = scmSettingsBaseParseString(&Base, pszOptions);
537 scmSettingsBaseDelete(&Base);
538 }
539 return rc;
540}
541
542/**
543 * Loads settings found in editor and SCM settings directives within the
544 * document (@a pStream).
545 *
546 * @returns IPRT status code.
547 * @param pBase The settings base to load settings into.
548 * @param pStream The stream to scan for settings directives.
549 */
550static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
551{
552 /** @todo Editor and SCM settings directives in documents. */
553 return VINF_SUCCESS;
554}
555
556/**
557 * Creates a new settings file struct, cloning @a pSettings.
558 *
559 * @returns IPRT status code.
560 * @param ppSettings Where to return the new struct.
561 * @param pSettingsBase The settings to inherit from.
562 */
563static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
564{
565 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
566 if (!pSettings)
567 return VERR_NO_MEMORY;
568 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
569 if (RT_SUCCESS(rc))
570 {
571 pSettings->pDown = NULL;
572 pSettings->pUp = NULL;
573 pSettings->paPairs = NULL;
574 pSettings->cPairs = 0;
575 *ppSettings = pSettings;
576 return VINF_SUCCESS;
577 }
578 RTMemFree(pSettings);
579 return rc;
580}
581
582/**
583 * Destroys a settings structure.
584 *
585 * @param pSettings The settings structure to destroy. NULL is OK.
586 */
587static void scmSettingsDestroy(PSCMSETTINGS pSettings)
588{
589 if (pSettings)
590 {
591 scmSettingsBaseDelete(&pSettings->Base);
592 for (size_t i = 0; i < pSettings->cPairs; i++)
593 {
594 RTStrFree(pSettings->paPairs[i].pszPattern);
595 RTStrFree(pSettings->paPairs[i].pszOptions);
596 pSettings->paPairs[i].pszPattern = NULL;
597 pSettings->paPairs[i].pszOptions = NULL;
598 }
599 RTMemFree(pSettings->paPairs);
600 pSettings->paPairs = NULL;
601 RTMemFree(pSettings);
602 }
603}
604
605/**
606 * Adds a pattern/options pair to the settings structure.
607 *
608 * @returns IPRT status code.
609 * @param pSettings The settings.
610 * @param pchLine The line containing the unparsed pair.
611 * @param cchLine The length of the line.
612 */
613static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
614{
615 /*
616 * Split the string.
617 */
618 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
619 if (!pchOptions)
620 return VERR_INVALID_PARAMETER;
621 size_t cchPattern = pchOptions - pchLine;
622 size_t cchOptions = cchLine - cchPattern - 1;
623 pchOptions++;
624
625 /* strip spaces everywhere */
626 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
627 cchPattern--;
628 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
629 cchPattern--, pchLine++;
630
631 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
632 cchOptions--;
633 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
634 cchOptions--, pchOptions++;
635
636 /* Quietly ignore empty patterns and empty options. */
637 if (!cchOptions || !cchPattern)
638 return VINF_SUCCESS;
639
640 /*
641 * Add the pair and verify the option string.
642 */
643 uint32_t iPair = pSettings->cPairs;
644 if ((iPair % 32) == 0)
645 {
646 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
647 if (!pvNew)
648 return VERR_NO_MEMORY;
649 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
650 }
651
652 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
653 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
654 int rc;
655 if ( pSettings->paPairs[iPair].pszPattern
656 && pSettings->paPairs[iPair].pszOptions)
657 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
658 else
659 rc = VERR_NO_MEMORY;
660 if (RT_SUCCESS(rc))
661 pSettings->cPairs = iPair + 1;
662 else
663 {
664 RTStrFree(pSettings->paPairs[iPair].pszPattern);
665 RTStrFree(pSettings->paPairs[iPair].pszOptions);
666 }
667 return rc;
668}
669
670/**
671 * Loads in the settings from @a pszFilename.
672 *
673 * @returns IPRT status code.
674 * @param pSettings Where to load the settings file.
675 * @param pszFilename The file to load.
676 */
677static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
678{
679 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
680
681 SCMSTREAM Stream;
682 int rc = ScmStreamInitForReading(&Stream, pszFilename);
683 if (RT_FAILURE(rc))
684 {
685 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
686 return rc;
687 }
688
689 SCMEOL enmEol;
690 const char *pchLine;
691 size_t cchLine;
692 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
693 {
694 /* Ignore leading spaces. */
695 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
696 pchLine++, cchLine--;
697
698 /* Ignore empty lines and comment lines. */
699 if (cchLine < 1 || *pchLine == '#')
700 continue;
701
702 /* What kind of line is it? */
703 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
704 if (pchColon)
705 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
706 else
707 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
708 if (RT_FAILURE(rc))
709 {
710 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
711 break;
712 }
713 }
714
715 if (RT_SUCCESS(rc))
716 {
717 rc = ScmStreamGetStatus(&Stream);
718 if (RT_FAILURE(rc))
719 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
720 }
721
722 ScmStreamDelete(&Stream);
723 return rc;
724}
725
726/**
727 * Parse the specified settings file creating a new settings struct from it.
728 *
729 * @returns IPRT status code
730 * @param ppSettings Where to return the new settings.
731 * @param pszFilename The file to parse.
732 * @param pSettingsBase The base settings we inherit from.
733 */
734static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
735{
736 PSCMSETTINGS pSettings;
737 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
738 if (RT_SUCCESS(rc))
739 {
740 rc = scmSettingsLoadFile(pSettings, pszFilename);
741 if (RT_SUCCESS(rc))
742 {
743 *ppSettings = pSettings;
744 return VINF_SUCCESS;
745 }
746
747 scmSettingsDestroy(pSettings);
748 }
749 *ppSettings = NULL;
750 return rc;
751}
752
753
754/**
755 * Create an initial settings structure when starting processing a new file or
756 * directory.
757 *
758 * This will look for .scm-settings files from the root and down to the
759 * specified directory, combining them into the returned settings structure.
760 *
761 * @returns IPRT status code.
762 * @param ppSettings Where to return the pointer to the top stack
763 * object.
764 * @param pBaseSettings The base settings we inherit from (globals
765 * typically).
766 * @param pszPath The absolute path to the new directory or file.
767 */
768static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
769{
770 *ppSettings = NULL; /* try shut up gcc. */
771
772 /*
773 * We'll be working with a stack copy of the path.
774 */
775 char szFile[RTPATH_MAX];
776 size_t cchDir = strlen(pszPath);
777 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
778 return VERR_FILENAME_TOO_LONG;
779
780 /*
781 * Create the bottom-most settings.
782 */
783 PSCMSETTINGS pSettings;
784 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
785 if (RT_FAILURE(rc))
786 return rc;
787
788 /*
789 * Enumerate the path components from the root and down. Load any setting
790 * files we find.
791 */
792 size_t cComponents = RTPathCountComponents(pszPath);
793 for (size_t i = 1; i <= cComponents; i++)
794 {
795 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
796 if (RT_SUCCESS(rc))
797 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
798 if (RT_FAILURE(rc))
799 break;
800 if (RTFileExists(szFile))
801 {
802 rc = scmSettingsLoadFile(pSettings, szFile);
803 if (RT_FAILURE(rc))
804 break;
805 }
806 }
807
808 if (RT_SUCCESS(rc))
809 *ppSettings = pSettings;
810 else
811 scmSettingsDestroy(pSettings);
812 return rc;
813}
814
815/**
816 * Pushes a new settings set onto the stack.
817 *
818 * @param ppSettingsStack The pointer to the pointer to the top stack
819 * element. This will be used as input and output.
820 * @param pSettings The settings to push onto the stack.
821 */
822static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
823{
824 PSCMSETTINGS pOld = *ppSettingsStack;
825 pSettings->pDown = pOld;
826 pSettings->pUp = NULL;
827 if (pOld)
828 pOld->pUp = pSettings;
829 *ppSettingsStack = pSettings;
830}
831
832/**
833 * Pushes the settings of the specified directory onto the stack.
834 *
835 * We will load any .scm-settings in the directory. A stack entry is added even
836 * if no settings file was found.
837 *
838 * @returns IPRT status code.
839 * @param ppSettingsStack The pointer to the pointer to the top stack
840 * element. This will be used as input and output.
841 * @param pszDir The directory to do this for.
842 */
843static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
844{
845 char szFile[RTPATH_MAX];
846 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
847 if (RT_SUCCESS(rc))
848 {
849 PSCMSETTINGS pSettings;
850 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
851 if (RT_SUCCESS(rc))
852 {
853 if (RTFileExists(szFile))
854 rc = scmSettingsLoadFile(pSettings, szFile);
855 if (RT_SUCCESS(rc))
856 {
857 scmSettingsStackPush(ppSettingsStack, pSettings);
858 return VINF_SUCCESS;
859 }
860
861 scmSettingsDestroy(pSettings);
862 }
863 }
864 return rc;
865}
866
867
868/**
869 * Pops a settings set off the stack.
870 *
871 * @returns The popped setttings.
872 * @param ppSettingsStack The pointer to the pointer to the top stack
873 * element. This will be used as input and output.
874 */
875static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
876{
877 PSCMSETTINGS pRet = *ppSettingsStack;
878 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
879 *ppSettingsStack = pNew;
880 if (pNew)
881 pNew->pUp = NULL;
882 if (pRet)
883 {
884 pRet->pUp = NULL;
885 pRet->pDown = NULL;
886 }
887 return pRet;
888}
889
890/**
891 * Pops and destroys the top entry of the stack.
892 *
893 * @param ppSettingsStack The pointer to the pointer to the top stack
894 * element. This will be used as input and output.
895 */
896static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
897{
898 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
899}
900
901/**
902 * Constructs the base settings for the specified file name.
903 *
904 * @returns IPRT status code.
905 * @param pSettingsStack The top element on the settings stack.
906 * @param pszFilename The file name.
907 * @param pszBasename The base name (pointer within @a pszFilename).
908 * @param cchBasename The length of the base name. (For passing to
909 * RTStrSimplePatternMultiMatch.)
910 * @param pBase Base settings to initialize.
911 */
912static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
913 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
914{
915 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
916 if (RT_SUCCESS(rc))
917 {
918 /* find the bottom entry in the stack. */
919 PCSCMSETTINGS pCur = pSettingsStack;
920 while (pCur->pDown)
921 pCur = pCur->pDown;
922
923 /* Work our way up thru the stack and look for matching pairs. */
924 while (pCur)
925 {
926 size_t const cPairs = pCur->cPairs;
927 if (cPairs)
928 {
929 for (size_t i = 0; i < cPairs; i++)
930 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
931 pszBasename, cchBasename, NULL)
932 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
933 pszFilename, RTSTR_MAX, NULL))
934 {
935 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
936 if (RT_FAILURE(rc))
937 break;
938 }
939 if (RT_FAILURE(rc))
940 break;
941 }
942
943 /* advance */
944 pCur = pCur->pUp;
945 }
946 }
947 if (RT_FAILURE(rc))
948 scmSettingsBaseDelete(pBase);
949 return rc;
950}
951
952
953/* -=-=-=-=-=- misc -=-=-=-=-=- */
954
955
956/**
957 * Prints a verbose message if the level is high enough.
958 *
959 * @param pState The rewrite state. Optional.
960 * @param iLevel The required verbosity level.
961 * @param pszFormat The message format string. Can be NULL if we
962 * only want to trigger the per file message.
963 * @param ... Format arguments.
964 */
965void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
966{
967 if (iLevel <= g_iVerbosity)
968 {
969 if (pState && !pState->fFirst)
970 {
971 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
972 pState->fFirst = true;
973 }
974 if (pszFormat)
975 {
976 RTPrintf(pState
977 ? "%s: info: "
978 : "%s: info: ",
979 g_szProgName);
980 va_list va;
981 va_start(va, pszFormat);
982 RTPrintfV(pszFormat, va);
983 va_end(va);
984 }
985 }
986}
987
988
989/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
990
991
992/**
993 * Processes a file.
994 *
995 * @returns IPRT status code.
996 * @param pState The rewriter state.
997 * @param pszFilename The file name.
998 * @param pszBasename The base name (pointer within @a pszFilename).
999 * @param cchBasename The length of the base name. (For passing to
1000 * RTStrSimplePatternMultiMatch.)
1001 * @param pBaseSettings The base settings to use. It's OK to modify
1002 * these.
1003 */
1004static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
1005 PSCMSETTINGSBASE pBaseSettings)
1006{
1007 /*
1008 * Do the file level filtering.
1009 */
1010 if ( pBaseSettings->pszFilterFiles
1011 && *pBaseSettings->pszFilterFiles
1012 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
1013 {
1014 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
1015 return VINF_SUCCESS;
1016 }
1017 if ( pBaseSettings->pszFilterOutFiles
1018 && *pBaseSettings->pszFilterOutFiles
1019 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
1020 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
1021 {
1022 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
1023 return VINF_SUCCESS;
1024 }
1025 if ( pBaseSettings->fOnlySvnFiles
1026 && !ScmSvnIsInWorkingCopy(pState))
1027 {
1028 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
1029 return VINF_SUCCESS;
1030 }
1031
1032 /*
1033 * Try find a matching rewrite config for this filename.
1034 */
1035 PCSCMCFGENTRY pCfg = NULL;
1036 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1037 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
1038 {
1039 pCfg = &g_aConfigs[iCfg];
1040 break;
1041 }
1042 if (!pCfg)
1043 {
1044 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
1045 return VINF_SUCCESS;
1046 }
1047 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
1048
1049 /*
1050 * Create an input stream from the file and check that it's text.
1051 */
1052 SCMSTREAM Stream1;
1053 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
1054 if (RT_FAILURE(rc))
1055 {
1056 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
1057 return rc;
1058 }
1059 if (ScmStreamIsText(&Stream1))
1060 {
1061 ScmVerbose(pState, 3, NULL);
1062
1063 /*
1064 * Gather SCM and editor settings from the stream.
1065 */
1066 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
1067 if (RT_SUCCESS(rc))
1068 {
1069 ScmStreamRewindForReading(&Stream1);
1070
1071 /*
1072 * Create two more streams for output and push the text thru all the
1073 * rewriters, switching the two streams around when something is
1074 * actually rewritten. Stream1 remains unchanged.
1075 */
1076 SCMSTREAM Stream2;
1077 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
1078 if (RT_SUCCESS(rc))
1079 {
1080 SCMSTREAM Stream3;
1081 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
1082 if (RT_SUCCESS(rc))
1083 {
1084 bool fModified = false;
1085 PSCMSTREAM pIn = &Stream1;
1086 PSCMSTREAM pOut = &Stream2;
1087 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
1088 {
1089 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
1090 if (fRc)
1091 {
1092 PSCMSTREAM pTmp = pOut;
1093 pOut = pIn == &Stream1 ? &Stream3 : pIn;
1094 pIn = pTmp;
1095 fModified = true;
1096 }
1097 ScmStreamRewindForReading(pIn);
1098 ScmStreamRewindForWriting(pOut);
1099 }
1100
1101 rc = ScmStreamGetStatus(&Stream1);
1102 if (RT_SUCCESS(rc))
1103 rc = ScmStreamGetStatus(&Stream2);
1104 if (RT_SUCCESS(rc))
1105 rc = ScmStreamGetStatus(&Stream3);
1106 if (RT_SUCCESS(rc))
1107 {
1108 /*
1109 * If rewritten, write it back to disk.
1110 */
1111 if (fModified)
1112 {
1113 if (!g_fDryRun)
1114 {
1115 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1116 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
1117 if (RT_FAILURE(rc))
1118 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
1119 }
1120 else
1121 {
1122 ScmVerbose(pState, 1, NULL);
1123 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
1124 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
1125 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1126 }
1127 }
1128
1129 /*
1130 * If pending SVN property changes, apply them.
1131 */
1132 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
1133 {
1134 if (!g_fDryRun)
1135 {
1136 rc = ScmSvnApplyChanges(pState);
1137 if (RT_FAILURE(rc))
1138 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
1139 }
1140 else
1141 ScmSvnDisplayChanges(pState);
1142 }
1143
1144 if (!fModified && !pState->cSvnPropChanges)
1145 ScmVerbose(pState, 3, "no change\n", pszFilename);
1146 }
1147 else
1148 RTMsgError("%s: stream error %Rrc\n", pszFilename);
1149 ScmStreamDelete(&Stream3);
1150 }
1151 else
1152 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1153 ScmStreamDelete(&Stream2);
1154 }
1155 else
1156 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1157 }
1158 else
1159 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
1160 }
1161 else
1162 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
1163 ScmStreamDelete(&Stream1);
1164
1165 return rc;
1166}
1167
1168/**
1169 * Processes a file.
1170 *
1171 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
1172 * directory recursion method.
1173 *
1174 * @returns IPRT status code.
1175 * @param pszFilename The file name.
1176 * @param pszBasename The base name (pointer within @a pszFilename).
1177 * @param cchBasename The length of the base name. (For passing to
1178 * RTStrSimplePatternMultiMatch.)
1179 * @param pSettingsStack The settings stack (pointer to the top element).
1180 */
1181static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
1182 PSCMSETTINGS pSettingsStack)
1183{
1184 SCMSETTINGSBASE Base;
1185 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
1186 if (RT_SUCCESS(rc))
1187 {
1188 SCMRWSTATE State;
1189 State.fFirst = false;
1190 State.pszFilename = pszFilename;
1191 State.cSvnPropChanges = 0;
1192 State.paSvnPropChanges = NULL;
1193
1194 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
1195
1196 size_t i = State.cSvnPropChanges;
1197 while (i-- > 0)
1198 {
1199 RTStrFree(State.paSvnPropChanges[i].pszName);
1200 RTStrFree(State.paSvnPropChanges[i].pszValue);
1201 }
1202 RTMemFree(State.paSvnPropChanges);
1203
1204 scmSettingsBaseDelete(&Base);
1205 }
1206 return rc;
1207}
1208
1209
1210/**
1211 * Tries to correct RTDIRENTRY_UNKNOWN.
1212 *
1213 * @returns Corrected type.
1214 * @param pszPath The path to the object in question.
1215 */
1216static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
1217{
1218 RTFSOBJINFO Info;
1219 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
1220 if (RT_FAILURE(rc))
1221 return RTDIRENTRYTYPE_UNKNOWN;
1222 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
1223 return RTDIRENTRYTYPE_DIRECTORY;
1224 if (RTFS_IS_FILE(Info.Attr.fMode))
1225 return RTDIRENTRYTYPE_FILE;
1226 return RTDIRENTRYTYPE_UNKNOWN;
1227}
1228
1229/**
1230 * Recurse into a sub-directory and process all the files and directories.
1231 *
1232 * @returns IPRT status code.
1233 * @param pszBuf Path buffer containing the directory path on
1234 * entry. This ends with a dot. This is passed
1235 * along when recursing in order to save stack space
1236 * and avoid needless copying.
1237 * @param cchDir Length of our path in pszbuf.
1238 * @param pEntry Directory entry buffer. This is also passed
1239 * along when recursing to save stack space.
1240 * @param pSettingsStack The settings stack (pointer to the top element).
1241 * @param iRecursion The recursion depth. This is used to restrict
1242 * the recursions.
1243 */
1244static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
1245 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
1246{
1247 int rc;
1248 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
1249
1250 /*
1251 * Make sure we stop somewhere.
1252 */
1253 if (iRecursion > 128)
1254 {
1255 RTMsgError("recursion too deep: %d\n", iRecursion);
1256 return VINF_SUCCESS; /* ignore */
1257 }
1258
1259 /*
1260 * Check if it's excluded by --only-svn-dir.
1261 */
1262 if (pSettingsStack->Base.fOnlySvnDirs)
1263 {
1264 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
1265 return VINF_SUCCESS;
1266 }
1267
1268 /*
1269 * Try open and read the directory.
1270 */
1271 PRTDIR pDir;
1272 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
1273 if (RT_FAILURE(rc))
1274 {
1275 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
1276 return rc;
1277 }
1278 for (;;)
1279 {
1280 /* Read the next entry. */
1281 rc = RTDirRead(pDir, pEntry, NULL);
1282 if (RT_FAILURE(rc))
1283 {
1284 if (rc == VERR_NO_MORE_FILES)
1285 rc = VINF_SUCCESS;
1286 else
1287 RTMsgError("RTDirRead -> %Rrc\n", rc);
1288 break;
1289 }
1290
1291 /* Skip '.' and '..'. */
1292 if ( pEntry->szName[0] == '.'
1293 && ( pEntry->cbName == 1
1294 || ( pEntry->cbName == 2
1295 && pEntry->szName[1] == '.')))
1296 continue;
1297
1298 /* Enter it into the buffer so we've got a full name to work
1299 with when needed. */
1300 if (pEntry->cbName + cchDir >= RTPATH_MAX)
1301 {
1302 RTMsgError("Skipping too long entry: %s", pEntry->szName);
1303 continue;
1304 }
1305 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
1306
1307 /* Figure the type. */
1308 RTDIRENTRYTYPE enmType = pEntry->enmType;
1309 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
1310 enmType = scmFigureUnknownType(pszBuf);
1311
1312 /* Process the file or directory, skip the rest. */
1313 if (enmType == RTDIRENTRYTYPE_FILE)
1314 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
1315 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
1316 {
1317 /* Append the dot for the benefit of the pattern matching. */
1318 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
1319 {
1320 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
1321 continue;
1322 }
1323 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
1324 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
1325
1326 if ( !pSettingsStack->Base.pszFilterOutDirs
1327 || !*pSettingsStack->Base.pszFilterOutDirs
1328 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1329 pEntry->szName, pEntry->cbName, NULL)
1330 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1331 pszBuf, cchSubDir, NULL)
1332 )
1333 )
1334 {
1335 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
1336 if (RT_SUCCESS(rc))
1337 {
1338 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
1339 scmSettingsStackPopAndDestroy(&pSettingsStack);
1340 }
1341 }
1342 }
1343 if (RT_FAILURE(rc))
1344 break;
1345 }
1346 RTDirClose(pDir);
1347 return rc;
1348
1349}
1350
1351/**
1352 * Process a directory tree.
1353 *
1354 * @returns IPRT status code.
1355 * @param pszDir The directory to start with. This is pointer to
1356 * a RTPATH_MAX sized buffer.
1357 */
1358static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
1359{
1360 /*
1361 * Setup the recursion.
1362 */
1363 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
1364 if (RT_SUCCESS(rc))
1365 {
1366 RTDIRENTRY Entry;
1367 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
1368 }
1369 else
1370 RTMsgError("RTPathAppend: %Rrc\n", rc);
1371 return rc;
1372}
1373
1374
1375/**
1376 * Processes a file or directory specified as an command line argument.
1377 *
1378 * @returns IPRT status code
1379 * @param pszSomething What we found in the command line arguments.
1380 * @param pSettingsStack The settings stack (pointer to the top element).
1381 */
1382static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
1383{
1384 char szBuf[RTPATH_MAX];
1385 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
1386 if (RT_SUCCESS(rc))
1387 {
1388 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
1389
1390 PSCMSETTINGS pSettings;
1391 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
1392 if (RT_SUCCESS(rc))
1393 {
1394 scmSettingsStackPush(&pSettingsStack, pSettings);
1395
1396 if (RTFileExists(szBuf))
1397 {
1398 const char *pszBasename = RTPathFilename(szBuf);
1399 if (pszBasename)
1400 {
1401 size_t cchBasename = strlen(pszBasename);
1402 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
1403 }
1404 else
1405 {
1406 RTMsgError("RTPathFilename: NULL\n");
1407 rc = VERR_IS_A_DIRECTORY;
1408 }
1409 }
1410 else
1411 rc = scmProcessDirTree(szBuf, pSettingsStack);
1412
1413 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
1414 Assert(pPopped == pSettings);
1415 scmSettingsDestroy(pSettings);
1416 }
1417 else
1418 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
1419 }
1420 else
1421 RTMsgError("RTPathAbs: %Rrc\n", rc);
1422 return rc;
1423}
1424
1425int main(int argc, char **argv)
1426{
1427 int rc = RTR3InitExe(argc, &argv, 0);
1428 if (RT_FAILURE(rc))
1429 return 1;
1430
1431 /*
1432 * Init the settings.
1433 */
1434 PSCMSETTINGS pSettings;
1435 rc = scmSettingsCreate(&pSettings, &g_Defaults);
1436 if (RT_FAILURE(rc))
1437 {
1438 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
1439 return 1;
1440 }
1441
1442 /*
1443 * Parse arguments and process input in order (because this is the only
1444 * thing that works at the moment).
1445 */
1446 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
1447 {
1448 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1449 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
1450 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
1451 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
1452 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1453 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1454 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1455 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1456 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1457 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1458 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1459 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1460 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1461 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1462 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1463 };
1464 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
1465
1466 RTGETOPTUNION ValueUnion;
1467 RTGETOPTSTATE GetOptState;
1468 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1469 AssertReleaseRCReturn(rc, 1);
1470 size_t cProcessed = 0;
1471
1472 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1473 {
1474 switch (rc)
1475 {
1476 case 'd':
1477 g_fDryRun = true;
1478 break;
1479 case 'D':
1480 g_fDryRun = false;
1481 break;
1482
1483 case 'f':
1484 g_pszFileFilter = ValueUnion.psz;
1485 break;
1486
1487 case 'h':
1488 RTPrintf("VirtualBox Source Code Massager\n"
1489 "\n"
1490 "Usage: %s [options] <files & dirs>\n"
1491 "\n"
1492 "Options:\n", g_szProgName);
1493 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
1494 {
1495 bool fAdvanceTwo = false;
1496 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
1497 {
1498 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
1499 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
1500 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
1501 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
1502 );
1503 if (fAdvanceTwo)
1504 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
1505 else
1506 RTPrintf(" %s\n", s_aOpts[i].pszLong);
1507 }
1508 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
1509 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
1510 else
1511 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
1512 switch (s_aOpts[i].iShort)
1513 {
1514 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
1515 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
1516 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
1517 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
1518 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
1519 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
1520 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
1521 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
1522 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
1523 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
1524 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
1525 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
1526 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
1527 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
1528 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
1529 }
1530 i += fAdvanceTwo;
1531 }
1532 return 1;
1533
1534 case 'q':
1535 g_iVerbosity = 0;
1536 break;
1537
1538 case 'v':
1539 g_iVerbosity++;
1540 break;
1541
1542 case 'V':
1543 {
1544 /* The following is assuming that svn does it's job here. */
1545 static const char s_szRev[] = "$Revision: 56324 $";
1546 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
1547 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
1548 return 0;
1549 }
1550
1551 case SCMOPT_DIFF_IGNORE_EOL:
1552 g_fDiffIgnoreEol = true;
1553 break;
1554 case SCMOPT_DIFF_NO_IGNORE_EOL:
1555 g_fDiffIgnoreEol = false;
1556 break;
1557
1558 case SCMOPT_DIFF_IGNORE_SPACE:
1559 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
1560 break;
1561 case SCMOPT_DIFF_NO_IGNORE_SPACE:
1562 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
1563 break;
1564
1565 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
1566 g_fDiffIgnoreLeadingWS = true;
1567 break;
1568 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
1569 g_fDiffIgnoreLeadingWS = false;
1570 break;
1571
1572 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
1573 g_fDiffIgnoreTrailingWS = true;
1574 break;
1575 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
1576 g_fDiffIgnoreTrailingWS = false;
1577 break;
1578
1579 case SCMOPT_DIFF_SPECIAL_CHARS:
1580 g_fDiffSpecialChars = true;
1581 break;
1582 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
1583 g_fDiffSpecialChars = false;
1584 break;
1585
1586 case VINF_GETOPT_NOT_OPTION:
1587 {
1588 if (!g_fDryRun)
1589 {
1590 if (!cProcessed)
1591 {
1592 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
1593 "%s: there is a slight risk that bugs or a full disk may cause\n"
1594 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
1595 "%s: all your changes already. If you didn't, then don't blame\n"
1596 "%s: anyone for not warning you!\n"
1597 "%s:\n"
1598 "%s: Press any key to continue...\n",
1599 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
1600 g_szProgName, g_szProgName);
1601 RTStrmGetCh(g_pStdIn);
1602 }
1603 cProcessed++;
1604 }
1605 rc = scmProcessSomething(ValueUnion.psz, pSettings);
1606 if (RT_FAILURE(rc))
1607 return rc;
1608 break;
1609 }
1610
1611 default:
1612 {
1613 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
1614 if (RT_SUCCESS(rc2))
1615 break;
1616 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
1617 return 2;
1618 return RTGetOptPrintError(rc, &ValueUnion);
1619 }
1620 }
1621 }
1622
1623 scmSettingsDestroy(pSettings);
1624 return 0;
1625}
1626
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