VirtualBox

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

Last change on this file since 69215 was 69215, checked in by vboxsync, 8 years ago

scm: Check for 'Contributed by' preceeding the copyright statement.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 76.9 KB
Line 
1/* $Id: scm.cpp 69215 2017-10-24 14:38:50Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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/dir.h>
25#include <iprt/env.h>
26#include <iprt/file.h>
27#include <iprt/err.h>
28#include <iprt/getopt.h>
29#include <iprt/initterm.h>
30#include <iprt/mem.h>
31#include <iprt/message.h>
32#include <iprt/param.h>
33#include <iprt/path.h>
34#include <iprt/process.h>
35#include <iprt/stream.h>
36#include <iprt/string.h>
37
38#include "scm.h"
39#include "scmdiff.h"
40
41
42/*********************************************************************************************************************************
43* Defined Constants And Macros *
44*********************************************************************************************************************************/
45/** The name of the settings files. */
46#define SCM_SETTINGS_FILENAME ".scm-settings"
47
48
49/*********************************************************************************************************************************
50* Structures and Typedefs *
51*********************************************************************************************************************************/
52
53/**
54 * Option identifiers.
55 *
56 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
57 * clear. So, the option setting a flag (boolean) will have an even
58 * number and the one clearing it will have an odd number.
59 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
60 */
61typedef enum SCMOPT
62{
63 SCMOPT_CONVERT_EOL = 10000,
64 SCMOPT_NO_CONVERT_EOL,
65 SCMOPT_CONVERT_TABS,
66 SCMOPT_NO_CONVERT_TABS,
67 SCMOPT_FORCE_FINAL_EOL,
68 SCMOPT_NO_FORCE_FINAL_EOL,
69 SCMOPT_FORCE_TRAILING_LINE,
70 SCMOPT_NO_FORCE_TRAILING_LINE,
71 SCMOPT_STRIP_TRAILING_BLANKS,
72 SCMOPT_NO_STRIP_TRAILING_BLANKS,
73 SCMOPT_STRIP_TRAILING_LINES,
74 SCMOPT_NO_STRIP_TRAILING_LINES,
75 SCMOPT_FIX_FLOWER_BOX_MARKERS,
76 SCMOPT_NO_FIX_FLOWER_BOX_MARKERS,
77 SCMOPT_FIX_TODOS,
78 SCMOPT_NO_FIX_TODOS,
79 SCMOPT_UPDATE_COPYRIGHT_YEAR,
80 SCMOPT_NO_UPDATE_COPYRIGHT_YEAR,
81 SCMOPT_EXTERNAL_COPYRIGHT,
82 SCMOPT_NO_EXTERNAL_COPYRIGHT,
83 SCMOPT_NO_UPDATE_LICENSE,
84 SCMOPT_LICENSE_OSE_GPL,
85 SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL,
86 SCMOPT_LICENSE_LGPL,
87 SCMOPT_LICENSE_MIT,
88 SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS,
89 SCMOPT_ONLY_SVN_DIRS,
90 SCMOPT_NOT_ONLY_SVN_DIRS,
91 SCMOPT_ONLY_SVN_FILES,
92 SCMOPT_NOT_ONLY_SVN_FILES,
93 SCMOPT_SET_SVN_EOL,
94 SCMOPT_DONT_SET_SVN_EOL,
95 SCMOPT_SET_SVN_EXECUTABLE,
96 SCMOPT_DONT_SET_SVN_EXECUTABLE,
97 SCMOPT_SET_SVN_KEYWORDS,
98 SCMOPT_DONT_SET_SVN_KEYWORDS,
99 SCMOPT_TAB_SIZE,
100 SCMOPT_WIDTH,
101 SCMOPT_FILTER_OUT_DIRS,
102 SCMOPT_FILTER_FILES,
103 SCMOPT_FILTER_OUT_FILES,
104 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
105 //
106 SCMOPT_DIFF_IGNORE_EOL,
107 SCMOPT_DIFF_NO_IGNORE_EOL,
108 SCMOPT_DIFF_IGNORE_SPACE,
109 SCMOPT_DIFF_NO_IGNORE_SPACE,
110 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
111 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
112 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
113 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
114 SCMOPT_DIFF_SPECIAL_CHARS,
115 SCMOPT_DIFF_NO_SPECIAL_CHARS,
116 SCMOPT_END
117} SCMOPT;
118
119
120/*********************************************************************************************************************************
121* Global Variables *
122*********************************************************************************************************************************/
123const char g_szTabSpaces[16+1] = " ";
124const char g_szAsterisks[255+1] =
125"****************************************************************************************************"
126"****************************************************************************************************"
127"*******************************************************";
128const char g_szSpaces[255+1] =
129" "
130" "
131" ";
132static const char g_szProgName[] = "scm";
133static const char *g_pszChangedSuff = "";
134static bool g_fDryRun = true;
135static bool g_fDiffSpecialChars = true;
136static bool g_fDiffIgnoreEol = false;
137static bool g_fDiffIgnoreLeadingWS = false;
138static bool g_fDiffIgnoreTrailingWS = false;
139static int g_iVerbosity = 2;//99; //0;
140uint32_t g_uYear = 0; /**< The current year. */
141/** @name Statistics
142 * @{ */
143static uint32_t g_cDirsProcessed = 0;
144static uint32_t g_cFilesProcessed = 0;
145static uint32_t g_cFilesModified = 0;
146static uint32_t g_cFilesSkipped = 0;
147static uint32_t g_cFilesNotInSvn = 0;
148static uint32_t g_cFilesNoRewriters = 0;
149static uint32_t g_cFilesBinaries = 0;
150/** @} */
151
152/** The global settings. */
153static SCMSETTINGSBASE const g_Defaults =
154{
155 /* .fConvertEol = */ true,
156 /* .fConvertTabs = */ true,
157 /* .fForceFinalEol = */ true,
158 /* .fForceTrailingLine = */ false,
159 /* .fStripTrailingBlanks = */ true,
160 /* .fStripTrailingLines = */ true,
161 /* .fFixFlowerBoxMarkers = */ true,
162 /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2,
163 /* .fFixTodos = */ true,
164 /* .fUpdateCopyrightYear = */ false,
165 /* .fExternalCopyright = */ false,
166 /* .enmUpdateLicense = */ kScmLicense_OseGpl,
167 /* .fOnlySvnFiles = */ false,
168 /* .fOnlySvnDirs = */ false,
169 /* .fSetSvnEol = */ false,
170 /* .fSetSvnExecutable = */ false,
171 /* .fSetSvnKeywords = */ false,
172 /* .cchTab = */ 8,
173 /* .cchWidth = */ 130,
174 /* .pszFilterFiles = */ (char *)"",
175 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
176 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
177};
178
179/** Option definitions for the base settings. */
180static RTGETOPTDEF g_aScmOpts[] =
181{
182 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
183 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
184 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
185 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
186 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
187 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
188 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
189 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
190 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
191 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
192 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
193 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
194 { "--min-blank-lines-before-flower-box-makers", SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 },
195 { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
196 { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
197 { "--fix-todos", SCMOPT_FIX_TODOS, RTGETOPT_REQ_NOTHING },
198 { "--no-fix-todos", SCMOPT_NO_FIX_TODOS, RTGETOPT_REQ_NOTHING },
199 { "--update-copyright-year", SCMOPT_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
200 { "--no-update-copyright-year", SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
201 { "--external-copyright", SCMOPT_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
202 { "--no-external-copyright", SCMOPT_NO_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
203 { "--no-update-license", SCMOPT_NO_UPDATE_LICENSE, RTGETOPT_REQ_NOTHING },
204 { "--license-ose-gpl", SCMOPT_LICENSE_OSE_GPL, RTGETOPT_REQ_NOTHING },
205 { "--license-ose-dual", SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, RTGETOPT_REQ_NOTHING },
206 { "--license-lgpl", SCMOPT_LICENSE_LGPL, RTGETOPT_REQ_NOTHING },
207 { "--license-mit", SCMOPT_LICENSE_MIT, RTGETOPT_REQ_NOTHING },
208 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
209 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
210 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
211 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
212 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
213 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
214 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
215 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
216 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
217 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
218 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
219 { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 },
220 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
221 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
222 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
223};
224
225/** Consider files matching the following patterns (base names only). */
226static const char *g_pszFileFilter = NULL;
227
228static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
229{
230 rewrite_SvnNoExecutable,
231 rewrite_Makefile_kup
232};
233
234static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
235{
236 rewrite_ForceNativeEol,
237 rewrite_StripTrailingBlanks,
238 rewrite_AdjustTrailingLines,
239 rewrite_SvnNoExecutable,
240 rewrite_SvnKeywords,
241 rewrite_Copyright_HashComment,
242 rewrite_Makefile_kmk
243};
244
245static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
246{
247 rewrite_ForceNativeEol,
248 rewrite_ExpandTabs,
249 rewrite_StripTrailingBlanks,
250 rewrite_AdjustTrailingLines,
251 rewrite_SvnNoExecutable,
252 rewrite_SvnKeywords,
253 rewrite_Copyright_CstyleComment,
254 rewrite_FixFlowerBoxMarkers,
255 rewrite_Fix_C_and_CPP_Todos,
256 rewrite_C_and_CPP
257};
258
259static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
260{
261 rewrite_ForceNativeEol,
262 rewrite_ExpandTabs,
263 rewrite_StripTrailingBlanks,
264 rewrite_AdjustTrailingLines,
265 rewrite_SvnNoExecutable,
266 rewrite_Copyright_CstyleComment,
267 rewrite_C_and_CPP
268};
269
270static PFNSCMREWRITER const g_aRewritersFor_RC[] =
271{
272 rewrite_ForceNativeEol,
273 rewrite_ExpandTabs,
274 rewrite_StripTrailingBlanks,
275 rewrite_AdjustTrailingLines,
276 rewrite_SvnNoExecutable,
277 rewrite_SvnKeywords,
278 rewrite_Copyright_CstyleComment,
279};
280
281static PFNSCMREWRITER const g_aRewritersFor_ASM[] =
282{
283 rewrite_ForceNativeEol,
284 rewrite_ExpandTabs,
285 rewrite_StripTrailingBlanks,
286 rewrite_AdjustTrailingLines,
287 rewrite_SvnNoExecutable,
288 rewrite_SvnKeywords,
289 rewrite_Copyright_SemicolonComment,
290};
291
292static PFNSCMREWRITER const g_aRewritersFor_DEF[] =
293{
294 rewrite_ForceNativeEol,
295 rewrite_ExpandTabs,
296 rewrite_StripTrailingBlanks,
297 rewrite_AdjustTrailingLines,
298 rewrite_SvnNoExecutable,
299 rewrite_SvnKeywords,
300 rewrite_Copyright_SemicolonComment,
301};
302
303static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
304{
305 rewrite_ForceLF,
306 rewrite_ExpandTabs,
307 rewrite_StripTrailingBlanks,
308 rewrite_Copyright_HashComment,
309};
310
311static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
312{
313 rewrite_ForceCRLF,
314 rewrite_ExpandTabs,
315 rewrite_StripTrailingBlanks,
316 rewrite_Copyright_RemComment,
317};
318
319static PFNSCMREWRITER const g_aRewritersFor_SedScripts[] =
320{
321 rewrite_ForceLF,
322 rewrite_ExpandTabs,
323 rewrite_StripTrailingBlanks,
324 rewrite_Copyright_HashComment,
325};
326
327static PFNSCMREWRITER const g_aRewritersFor_Python[] =
328{
329 /** @todo rewrite_ForceLFIfExecutable */
330 rewrite_ExpandTabs,
331 rewrite_StripTrailingBlanks,
332 rewrite_AdjustTrailingLines,
333 rewrite_SvnKeywords,
334 rewrite_Copyright_PythonComment,
335};
336
337static PFNSCMREWRITER const g_aRewritersFor_ScmSettings[] =
338{
339 rewrite_ForceNativeEol,
340 rewrite_ExpandTabs,
341 rewrite_StripTrailingBlanks,
342 rewrite_AdjustTrailingLines,
343 rewrite_SvnNoExecutable,
344 rewrite_SvnKeywords,
345 rewrite_Copyright_HashComment,
346};
347
348
349static SCMCFGENTRY const g_aConfigs[] =
350{
351 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
352 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
353 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm" },
354 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
355 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
356 { RT_ELEMENTS(g_aRewritersFor_ASM), &g_aRewritersFor_ASM[0], "*.asm|*.mac" },
357 { RT_ELEMENTS(g_aRewritersFor_DEF), &g_aRewritersFor_DEF[0], "*.def" },
358 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
359 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
360 { RT_ELEMENTS(g_aRewritersFor_SedScripts), &g_aRewritersFor_SedScripts[0], "*.sed" },
361 { RT_ELEMENTS(g_aRewritersFor_Python), &g_aRewritersFor_Python[0], "*.py" },
362 { RT_ELEMENTS(g_aRewritersFor_ScmSettings), &g_aRewritersFor_ScmSettings[0], "*.scm-settings" },
363};
364
365
366
367/* -=-=-=-=-=- settings -=-=-=-=-=- */
368
369
370/**
371 * Init a settings structure with settings from @a pSrc.
372 *
373 * @returns IPRT status code
374 * @param pSettings The settings.
375 * @param pSrc The source settings.
376 */
377static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
378{
379 *pSettings = *pSrc;
380
381 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
382 if (RT_SUCCESS(rc))
383 {
384 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
385 if (RT_SUCCESS(rc))
386 {
387 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
388 if (RT_SUCCESS(rc))
389 return VINF_SUCCESS;
390
391 RTStrFree(pSettings->pszFilterOutFiles);
392 }
393 RTStrFree(pSettings->pszFilterFiles);
394 }
395
396 pSettings->pszFilterFiles = NULL;
397 pSettings->pszFilterOutFiles = NULL;
398 pSettings->pszFilterOutDirs = NULL;
399 return rc;
400}
401
402/**
403 * Init a settings structure.
404 *
405 * @returns IPRT status code
406 * @param pSettings The settings.
407 */
408static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
409{
410 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
411}
412
413/**
414 * Deletes the settings, i.e. free any dynamically allocated content.
415 *
416 * @param pSettings The settings.
417 */
418static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
419{
420 if (pSettings)
421 {
422 Assert(pSettings->cchTab != UINT8_MAX);
423 pSettings->cchTab = UINT8_MAX;
424
425 RTStrFree(pSettings->pszFilterFiles);
426 pSettings->pszFilterFiles = NULL;
427
428 RTStrFree(pSettings->pszFilterOutFiles);
429 pSettings->pszFilterOutFiles = NULL;
430
431 RTStrFree(pSettings->pszFilterOutDirs);
432 pSettings->pszFilterOutDirs = NULL;
433 }
434}
435
436
437/**
438 * Processes a RTGetOpt result.
439 *
440 * @retval VINF_SUCCESS if handled.
441 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
442 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
443 *
444 * @param pSettings The settings to change.
445 * @param rc The RTGetOpt return value.
446 * @param pValueUnion The RTGetOpt value union.
447 * @param pchDir The absolute path to the directory relative
448 * components in pchLine should be relative to.
449 * @param cchDir The length of the @a pchDir string.
450 */
451static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion,
452 const char *pchDir, size_t cchDir)
453{
454 Assert(pchDir[cchDir - 1] == '/');
455
456 switch (rc)
457 {
458 case SCMOPT_CONVERT_EOL:
459 pSettings->fConvertEol = true;
460 return VINF_SUCCESS;
461 case SCMOPT_NO_CONVERT_EOL:
462 pSettings->fConvertEol = false;
463 return VINF_SUCCESS;
464
465 case SCMOPT_CONVERT_TABS:
466 pSettings->fConvertTabs = true;
467 return VINF_SUCCESS;
468 case SCMOPT_NO_CONVERT_TABS:
469 pSettings->fConvertTabs = false;
470 return VINF_SUCCESS;
471
472 case SCMOPT_FORCE_FINAL_EOL:
473 pSettings->fForceFinalEol = true;
474 return VINF_SUCCESS;
475 case SCMOPT_NO_FORCE_FINAL_EOL:
476 pSettings->fForceFinalEol = false;
477 return VINF_SUCCESS;
478
479 case SCMOPT_FORCE_TRAILING_LINE:
480 pSettings->fForceTrailingLine = true;
481 return VINF_SUCCESS;
482 case SCMOPT_NO_FORCE_TRAILING_LINE:
483 pSettings->fForceTrailingLine = false;
484 return VINF_SUCCESS;
485
486
487 case SCMOPT_STRIP_TRAILING_BLANKS:
488 pSettings->fStripTrailingBlanks = true;
489 return VINF_SUCCESS;
490 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
491 pSettings->fStripTrailingBlanks = false;
492 return VINF_SUCCESS;
493
494 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS:
495 pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8;
496 return VINF_SUCCESS;
497
498
499 case SCMOPT_STRIP_TRAILING_LINES:
500 pSettings->fStripTrailingLines = true;
501 return VINF_SUCCESS;
502 case SCMOPT_NO_STRIP_TRAILING_LINES:
503 pSettings->fStripTrailingLines = false;
504 return VINF_SUCCESS;
505
506 case SCMOPT_FIX_FLOWER_BOX_MARKERS:
507 pSettings->fFixFlowerBoxMarkers = true;
508 return VINF_SUCCESS;
509 case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS:
510 pSettings->fFixFlowerBoxMarkers = false;
511 return VINF_SUCCESS;
512
513 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
514 pSettings->fUpdateCopyrightYear = true;
515 return VINF_SUCCESS;
516 case SCMOPT_NO_UPDATE_COPYRIGHT_YEAR:
517 pSettings->fUpdateCopyrightYear = false;
518 return VINF_SUCCESS;
519
520 case SCMOPT_EXTERNAL_COPYRIGHT:
521 pSettings->fExternalCopyright = true;
522 return VINF_SUCCESS;
523 case SCMOPT_NO_EXTERNAL_COPYRIGHT:
524 pSettings->fExternalCopyright = false;
525 return VINF_SUCCESS;
526
527 case SCMOPT_NO_UPDATE_LICENSE:
528 pSettings->enmUpdateLicense = kScmLicense_LeaveAlone;
529 return VINF_SUCCESS;
530 case SCMOPT_LICENSE_OSE_GPL:
531 pSettings->enmUpdateLicense = kScmLicense_OseGpl;
532 return VINF_SUCCESS;
533 case SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL:
534 pSettings->enmUpdateLicense = kScmLicense_OseDualGplCddl;
535 return VINF_SUCCESS;
536 case SCMOPT_LICENSE_LGPL:
537 pSettings->enmUpdateLicense = kScmLicense_Lgpl;
538 return VINF_SUCCESS;
539 case SCMOPT_LICENSE_MIT:
540 pSettings->enmUpdateLicense = kScmLicense_Mit;
541 return VINF_SUCCESS;
542
543 case SCMOPT_ONLY_SVN_DIRS:
544 pSettings->fOnlySvnDirs = true;
545 return VINF_SUCCESS;
546 case SCMOPT_NOT_ONLY_SVN_DIRS:
547 pSettings->fOnlySvnDirs = false;
548 return VINF_SUCCESS;
549
550 case SCMOPT_ONLY_SVN_FILES:
551 pSettings->fOnlySvnFiles = true;
552 return VINF_SUCCESS;
553 case SCMOPT_NOT_ONLY_SVN_FILES:
554 pSettings->fOnlySvnFiles = false;
555 return VINF_SUCCESS;
556
557 case SCMOPT_SET_SVN_EOL:
558 pSettings->fSetSvnEol = true;
559 return VINF_SUCCESS;
560 case SCMOPT_DONT_SET_SVN_EOL:
561 pSettings->fSetSvnEol = false;
562 return VINF_SUCCESS;
563
564 case SCMOPT_SET_SVN_EXECUTABLE:
565 pSettings->fSetSvnExecutable = true;
566 return VINF_SUCCESS;
567 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
568 pSettings->fSetSvnExecutable = false;
569 return VINF_SUCCESS;
570
571 case SCMOPT_SET_SVN_KEYWORDS:
572 pSettings->fSetSvnKeywords = true;
573 return VINF_SUCCESS;
574 case SCMOPT_DONT_SET_SVN_KEYWORDS:
575 pSettings->fSetSvnKeywords = false;
576 return VINF_SUCCESS;
577
578 case SCMOPT_TAB_SIZE:
579 if ( pValueUnion->u8 < 1
580 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
581 {
582 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
583 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
584 return VERR_OUT_OF_RANGE;
585 }
586 pSettings->cchTab = pValueUnion->u8;
587 return VINF_SUCCESS;
588
589 case SCMOPT_WIDTH:
590 if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200)
591 {
592 RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8);
593 return VERR_OUT_OF_RANGE;
594 }
595 pSettings->cchWidth = pValueUnion->u8;
596 return VINF_SUCCESS;
597
598 case SCMOPT_FILTER_OUT_DIRS:
599 case SCMOPT_FILTER_FILES:
600 case SCMOPT_FILTER_OUT_FILES:
601 {
602 char **ppsz = NULL;
603 switch (rc)
604 {
605 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
606 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
607 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
608 }
609
610 /*
611 * An empty string zaps the current list.
612 */
613 if (!*pValueUnion->psz)
614 return RTStrATruncate(ppsz, 0);
615
616 /*
617 * Non-empty strings are appended to the pattern list.
618 *
619 * Strip leading and trailing pattern separators before attempting
620 * to append it. If it's just separators, don't do anything.
621 */
622 const char *pszSrc = pValueUnion->psz;
623 while (*pszSrc == '|')
624 pszSrc++;
625 size_t cchSrc = strlen(pszSrc);
626 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
627 cchSrc--;
628 if (!cchSrc)
629 return VINF_SUCCESS;
630
631 /* Append it pattern by pattern, turning settings-relative paths into absolute ones. */
632 while (cchSrc > 0)
633 {
634 const char *pszEnd = (const char *)memchr(pszSrc, '|', cchSrc);
635 size_t cchPattern = pszEnd ? pszEnd - pszSrc : cchSrc;
636 int rc2;
637 if (*pszSrc == '/')
638 rc2 = RTStrAAppendExN(ppsz, 3,
639 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
640 pchDir, cchDir - 1,
641 pszSrc, cchPattern);
642 else
643 rc2 = RTStrAAppendExN(ppsz, 2,
644 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
645 pszSrc, cchPattern);
646 if (RT_FAILURE(rc2))
647 return rc2;
648
649 /* next */
650 cchSrc -= cchPattern;
651 if (!cchSrc)
652 return VINF_SUCCESS;
653 cchSrc -= 1;
654 pszSrc += cchPattern + 1;
655 }
656 }
657
658 default:
659 return VERR_GETOPT_UNKNOWN_OPTION;
660 }
661}
662
663/**
664 * Parses an option string.
665 *
666 * @returns IPRT status code.
667 * @param pBase The base settings structure to apply the options
668 * to.
669 * @param pszOptions The options to parse.
670 * @param pchDir The absolute path to the directory relative
671 * components in pchLine should be relative to.
672 * @param cchDir The length of the @a pchDir string.
673 */
674static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir)
675{
676 int cArgs;
677 char **papszArgs;
678 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
679 if (RT_SUCCESS(rc))
680 {
681 RTGETOPTUNION ValueUnion;
682 RTGETOPTSTATE GetOptState;
683 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
684 if (RT_SUCCESS(rc))
685 {
686 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
687 {
688 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir);
689 if (RT_FAILURE(rc))
690 break;
691 }
692 }
693 RTGetOptArgvFree(papszArgs);
694 }
695
696 return rc;
697}
698
699/**
700 * Parses an unterminated option string.
701 *
702 * @returns IPRT status code.
703 * @param pBase The base settings structure to apply the options
704 * to.
705 * @param pchLine The line.
706 * @param cchLine The line length.
707 * @param pchDir The absolute path to the directory relative
708 * components in pchLine should be relative to.
709 * @param cchDir The length of the @a pchDir string.
710 */
711static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine,
712 const char *pchDir, size_t cchDir)
713{
714 char *pszLine = RTStrDupN(pchLine, cchLine);
715 if (!pszLine)
716 return VERR_NO_MEMORY;
717 int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir);
718 RTStrFree(pszLine);
719 return rc;
720}
721
722/**
723 * Verifies the options string.
724 *
725 * @returns IPRT status code.
726 * @param pszOptions The options to verify .
727 */
728static int scmSettingsBaseVerifyString(const char *pszOptions)
729{
730 SCMSETTINGSBASE Base;
731 int rc = scmSettingsBaseInit(&Base);
732 if (RT_SUCCESS(rc))
733 {
734 rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1);
735 scmSettingsBaseDelete(&Base);
736 }
737 return rc;
738}
739
740/**
741 * Loads settings found in editor and SCM settings directives within the
742 * document (@a pStream).
743 *
744 * @returns IPRT status code.
745 * @param pBase The settings base to load settings into.
746 * @param pStream The stream to scan for settings directives.
747 */
748static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
749{
750 /** @todo Editor and SCM settings directives in documents. */
751 RT_NOREF2(pBase, pStream);
752 return VINF_SUCCESS;
753}
754
755/**
756 * Creates a new settings file struct, cloning @a pSettings.
757 *
758 * @returns IPRT status code.
759 * @param ppSettings Where to return the new struct.
760 * @param pSettingsBase The settings to inherit from.
761 */
762static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
763{
764 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
765 if (!pSettings)
766 return VERR_NO_MEMORY;
767 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
768 if (RT_SUCCESS(rc))
769 {
770 pSettings->pDown = NULL;
771 pSettings->pUp = NULL;
772 pSettings->paPairs = NULL;
773 pSettings->cPairs = 0;
774 *ppSettings = pSettings;
775 return VINF_SUCCESS;
776 }
777 RTMemFree(pSettings);
778 return rc;
779}
780
781/**
782 * Destroys a settings structure.
783 *
784 * @param pSettings The settings structure to destroy. NULL is OK.
785 */
786static void scmSettingsDestroy(PSCMSETTINGS pSettings)
787{
788 if (pSettings)
789 {
790 scmSettingsBaseDelete(&pSettings->Base);
791 for (size_t i = 0; i < pSettings->cPairs; i++)
792 {
793 RTStrFree(pSettings->paPairs[i].pszPattern);
794 RTStrFree(pSettings->paPairs[i].pszOptions);
795 RTStrFree(pSettings->paPairs[i].pszRelativeTo);
796 pSettings->paPairs[i].pszPattern = NULL;
797 pSettings->paPairs[i].pszOptions = NULL;
798 pSettings->paPairs[i].pszRelativeTo = NULL;
799 }
800 RTMemFree(pSettings->paPairs);
801 pSettings->paPairs = NULL;
802 RTMemFree(pSettings);
803 }
804}
805
806/**
807 * Adds a pattern/options pair to the settings structure.
808 *
809 * @returns IPRT status code.
810 * @param pSettings The settings.
811 * @param pchLine The line containing the unparsed pair.
812 * @param cchLine The length of the line.
813 * @param offColon The offset of the colon into the line.
814 * @param pchDir The absolute path to the directory relative
815 * components in pchLine should be relative to.
816 * @param cchDir The length of the @a pchDir string.
817 */
818static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon,
819 const char *pchDir, size_t cchDir)
820{
821 Assert(pchLine[offColon] == ':' && offColon < cchLine);
822 Assert(pchDir[cchDir - 1] == '/');
823
824 /*
825 * Split the string.
826 */
827 size_t cchPattern = offColon;
828 size_t cchOptions = cchLine - cchPattern - 1;
829
830 /* strip spaces everywhere */
831 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
832 cchPattern--;
833 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
834 cchPattern--, pchLine++;
835
836 const char *pchOptions = &pchLine[offColon + 1];
837 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
838 cchOptions--;
839 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
840 cchOptions--, pchOptions++;
841
842 /* Quietly ignore empty patterns and empty options. */
843 if (!cchOptions || !cchPattern)
844 return VINF_SUCCESS;
845
846 /*
847 * Prepair the pair and verify the option string.
848 */
849 uint32_t iPair = pSettings->cPairs;
850 if ((iPair % 32) == 0)
851 {
852 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
853 if (!pvNew)
854 return VERR_NO_MEMORY;
855 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
856 }
857
858 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
859 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
860 pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir);
861 int rc;
862 if ( pSettings->paPairs[iPair].pszPattern
863 && pSettings->paPairs[iPair].pszOptions
864 && pSettings->paPairs[iPair].pszRelativeTo)
865 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
866 else
867 rc = VERR_NO_MEMORY;
868
869 /*
870 * If it checked out fine, expand any relative paths in the pattern.
871 */
872 if (RT_SUCCESS(rc))
873 {
874 size_t cRelativePaths = 0;
875 const char *pszSrc = pSettings->paPairs[iPair].pszPattern;
876 for (;;)
877 {
878 if (*pszSrc == '/')
879 cRelativePaths++;
880 pszSrc = strchr(pszSrc, '|');
881 if (!pszSrc)
882 break;
883 pszSrc++;
884 }
885 if (cRelativePaths > 0)
886 {
887 char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1);
888 if (pszNewPattern)
889 {
890 char *pszDst = pszNewPattern;
891 pszSrc = pSettings->paPairs[iPair].pszPattern;
892 for (;;)
893 {
894 if (*pszSrc == '/')
895 {
896 memcpy(pszDst, pchDir, cchDir);
897 pszDst += cchDir;
898 pszSrc += 1;
899 }
900
901 /* Look for the next relative path. */
902 const char *pszSrcNext = strchr(pszSrc, '|');
903 while (pszSrcNext && pszSrcNext[1] != '/')
904 pszSrcNext = strchr(pszSrcNext, '|');
905 if (!pszSrcNext)
906 break;
907
908 /* Copy stuff between current and the next path. */
909 pszSrcNext++;
910 memcpy(pszDst, pszSrc, pszSrcNext - pszSrc);
911 pszDst += pszSrcNext - pszSrc;
912 pszSrc = pszSrcNext;
913 }
914
915 /* Copy the final portion and replace the pattern. */
916 strcpy(pszDst, pszSrc);
917
918 RTStrFree(pSettings->paPairs[iPair].pszPattern);
919 pSettings->paPairs[iPair].pszPattern = pszNewPattern;
920 }
921 else
922 rc = VERR_NO_MEMORY;
923 }
924 }
925 if (RT_SUCCESS(rc))
926 /*
927 * Commit the pair.
928 */
929 pSettings->cPairs = iPair + 1;
930 else
931 {
932 RTStrFree(pSettings->paPairs[iPair].pszPattern);
933 RTStrFree(pSettings->paPairs[iPair].pszOptions);
934 RTStrFree(pSettings->paPairs[iPair].pszRelativeTo);
935 }
936 return rc;
937}
938
939/**
940 * Loads in the settings from @a pszFilename.
941 *
942 * @returns IPRT status code.
943 * @param pSettings Where to load the settings file.
944 * @param pszFilename The file to load. ASSUMED to be absolute.
945 * @param offName The offset of the name part in pszFilename.
946 */
947static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename, size_t offName)
948{
949 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
950
951#ifdef RT_STRICT
952 /* Check absolute path assumption. */
953 char szTmp[RTPATH_MAX];
954 int rcTmp = RTPathAbs(pszFilename, szTmp, sizeof(szTmp));
955 AssertMsg(RT_SUCCESS(rcTmp) && RTPathCompare(szTmp, pszFilename) == 0, ("rcTmp=%Rrc pszFilename='%s'\n", rcTmp, pszFilename));
956#endif
957
958 SCMSTREAM Stream;
959 int rc = ScmStreamInitForReading(&Stream, pszFilename);
960 if (RT_FAILURE(rc))
961 {
962 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
963 return rc;
964 }
965
966 SCMEOL enmEol;
967 const char *pchLine;
968 size_t cchLine;
969 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
970 {
971 /* Ignore leading spaces. */
972 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
973 pchLine++, cchLine--;
974
975 /* Ignore empty lines and comment lines. */
976 if (cchLine < 1 || *pchLine == '#')
977 continue;
978
979 /* What kind of line is it? */
980 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
981 if (pchColon)
982 rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, pszFilename, offName);
983 else
984 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, pszFilename, offName);
985 if (RT_FAILURE(rc))
986 {
987 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
988 break;
989 }
990 }
991
992 if (RT_SUCCESS(rc))
993 {
994 rc = ScmStreamGetStatus(&Stream);
995 if (RT_FAILURE(rc))
996 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
997 }
998
999 ScmStreamDelete(&Stream);
1000 return rc;
1001}
1002
1003#if 0 /* unused */
1004/**
1005 * Parse the specified settings file creating a new settings struct from it.
1006 *
1007 * @returns IPRT status code
1008 * @param ppSettings Where to return the new settings.
1009 * @param pszFilename The file to parse.
1010 * @param pSettingsBase The base settings we inherit from.
1011 */
1012static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
1013{
1014 PSCMSETTINGS pSettings;
1015 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
1016 if (RT_SUCCESS(rc))
1017 {
1018 rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename);
1019 if (RT_SUCCESS(rc))
1020 {
1021 *ppSettings = pSettings;
1022 return VINF_SUCCESS;
1023 }
1024
1025 scmSettingsDestroy(pSettings);
1026 }
1027 *ppSettings = NULL;
1028 return rc;
1029}
1030#endif
1031
1032
1033/**
1034 * Create an initial settings structure when starting processing a new file or
1035 * directory.
1036 *
1037 * This will look for .scm-settings files from the root and down to the
1038 * specified directory, combining them into the returned settings structure.
1039 *
1040 * @returns IPRT status code.
1041 * @param ppSettings Where to return the pointer to the top stack
1042 * object.
1043 * @param pBaseSettings The base settings we inherit from (globals
1044 * typically).
1045 * @param pszPath The absolute path to the new directory or file.
1046 */
1047static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
1048{
1049 *ppSettings = NULL; /* try shut up gcc. */
1050
1051 /*
1052 * We'll be working with a stack copy of the path.
1053 */
1054 char szFile[RTPATH_MAX];
1055 size_t cchDir = strlen(pszPath);
1056 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
1057 return VERR_FILENAME_TOO_LONG;
1058
1059 /*
1060 * Create the bottom-most settings.
1061 */
1062 PSCMSETTINGS pSettings;
1063 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
1064 if (RT_FAILURE(rc))
1065 return rc;
1066
1067 /*
1068 * Enumerate the path components from the root and down. Load any setting
1069 * files we find.
1070 */
1071 size_t cComponents = RTPathCountComponents(pszPath);
1072 for (size_t i = 1; i <= cComponents; i++)
1073 {
1074 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
1075 if (RT_SUCCESS(rc))
1076 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
1077 if (RT_FAILURE(rc))
1078 break;
1079 RTPathChangeToUnixSlashes(szFile, true);
1080
1081 if (RTFileExists(szFile))
1082 {
1083 rc = scmSettingsLoadFile(pSettings, szFile, RTPathFilename(szFile) - &szFile[0]);
1084 if (RT_FAILURE(rc))
1085 break;
1086 }
1087 }
1088
1089 if (RT_SUCCESS(rc))
1090 *ppSettings = pSettings;
1091 else
1092 scmSettingsDestroy(pSettings);
1093 return rc;
1094}
1095
1096/**
1097 * Pushes a new settings set onto the stack.
1098 *
1099 * @param ppSettingsStack The pointer to the pointer to the top stack
1100 * element. This will be used as input and output.
1101 * @param pSettings The settings to push onto the stack.
1102 */
1103static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
1104{
1105 PSCMSETTINGS pOld = *ppSettingsStack;
1106 pSettings->pDown = pOld;
1107 pSettings->pUp = NULL;
1108 if (pOld)
1109 pOld->pUp = pSettings;
1110 *ppSettingsStack = pSettings;
1111}
1112
1113/**
1114 * Pushes the settings of the specified directory onto the stack.
1115 *
1116 * We will load any .scm-settings in the directory. A stack entry is added even
1117 * if no settings file was found.
1118 *
1119 * @returns IPRT status code.
1120 * @param ppSettingsStack The pointer to the pointer to the top stack
1121 * element. This will be used as input and output.
1122 * @param pszDir The directory to do this for.
1123 */
1124static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
1125{
1126 char szFile[RTPATH_MAX];
1127 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
1128 if (RT_SUCCESS(rc))
1129 {
1130 RTPathChangeToUnixSlashes(szFile, true);
1131
1132 PSCMSETTINGS pSettings;
1133 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
1134 if (RT_SUCCESS(rc))
1135 {
1136 if (RTFileExists(szFile))
1137 rc = scmSettingsLoadFile(pSettings, szFile, RTPathFilename(szFile) - &szFile[0]);
1138 if (RT_SUCCESS(rc))
1139 {
1140 scmSettingsStackPush(ppSettingsStack, pSettings);
1141 return VINF_SUCCESS;
1142 }
1143
1144 scmSettingsDestroy(pSettings);
1145 }
1146 }
1147 return rc;
1148}
1149
1150
1151/**
1152 * Pops a settings set off the stack.
1153 *
1154 * @returns The popped setttings.
1155 * @param ppSettingsStack The pointer to the pointer to the top stack
1156 * element. This will be used as input and output.
1157 */
1158static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
1159{
1160 PSCMSETTINGS pRet = *ppSettingsStack;
1161 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
1162 *ppSettingsStack = pNew;
1163 if (pNew)
1164 pNew->pUp = NULL;
1165 if (pRet)
1166 {
1167 pRet->pUp = NULL;
1168 pRet->pDown = NULL;
1169 }
1170 return pRet;
1171}
1172
1173/**
1174 * Pops and destroys the top entry of the stack.
1175 *
1176 * @param ppSettingsStack The pointer to the pointer to the top stack
1177 * element. This will be used as input and output.
1178 */
1179static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
1180{
1181 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
1182}
1183
1184/**
1185 * Constructs the base settings for the specified file name.
1186 *
1187 * @returns IPRT status code.
1188 * @param pSettingsStack The top element on the settings stack.
1189 * @param pszFilename The file name.
1190 * @param pszBasename The base name (pointer within @a pszFilename).
1191 * @param cchBasename The length of the base name. (For passing to
1192 * RTStrSimplePatternMultiMatch.)
1193 * @param pBase Base settings to initialize.
1194 */
1195static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
1196 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
1197{
1198 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename);
1199
1200 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
1201 if (RT_SUCCESS(rc))
1202 {
1203 /* find the bottom entry in the stack. */
1204 PCSCMSETTINGS pCur = pSettingsStack;
1205 while (pCur->pDown)
1206 pCur = pCur->pDown;
1207
1208 /* Work our way up thru the stack and look for matching pairs. */
1209 while (pCur)
1210 {
1211 size_t const cPairs = pCur->cPairs;
1212 if (cPairs)
1213 {
1214 for (size_t i = 0; i < cPairs; i++)
1215 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1216 pszBasename, cchBasename, NULL)
1217 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1218 pszFilename, RTSTR_MAX, NULL))
1219 {
1220 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n",
1221 pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions);
1222 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions,
1223 pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo));
1224 if (RT_FAILURE(rc))
1225 break;
1226 }
1227 if (RT_FAILURE(rc))
1228 break;
1229 }
1230
1231 /* advance */
1232 pCur = pCur->pUp;
1233 }
1234 }
1235 if (RT_FAILURE(rc))
1236 scmSettingsBaseDelete(pBase);
1237 return rc;
1238}
1239
1240
1241/* -=-=-=-=-=- misc -=-=-=-=-=- */
1242
1243
1244/**
1245 * Prints the per file banner needed and the message level is high enough.
1246 *
1247 * @param pState The rewrite state.
1248 * @param iLevel The required verbosity level.
1249 */
1250void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel)
1251{
1252 if (iLevel <= g_iVerbosity && !pState->fFirst)
1253 {
1254 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1255 pState->fFirst = true;
1256 }
1257}
1258
1259
1260/**
1261 * Prints a verbose message if the level is high enough.
1262 *
1263 * @param pState The rewrite state. Optional.
1264 * @param iLevel The required verbosity level.
1265 * @param pszFormat The message format string. Can be NULL if we
1266 * only want to trigger the per file message.
1267 * @param ... Format arguments.
1268 */
1269void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
1270{
1271 if (iLevel <= g_iVerbosity)
1272 {
1273 if (pState && !pState->fFirst)
1274 {
1275 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1276 pState->fFirst = true;
1277 }
1278 RTPrintf(pState
1279 ? "%s: info: "
1280 : "%s: info: ",
1281 g_szProgName);
1282 va_list va;
1283 va_start(va, pszFormat);
1284 RTPrintfV(pszFormat, va);
1285 va_end(va);
1286 }
1287}
1288
1289
1290/**
1291 * Prints an error message.
1292 *
1293 * @returns false
1294 * @param pState The rewrite state. Optional.
1295 * @param rc The error code.
1296 * @param pszFormat The message format string.
1297 * @param ... Format arguments.
1298 */
1299bool ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...)
1300{
1301 if (RT_SUCCESS(pState->rc))
1302 pState->rc = rc;
1303
1304 if (!pState->fFirst)
1305 {
1306 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1307 pState->fFirst = true;
1308 }
1309 va_list va;
1310 va_start(va, pszFormat);
1311 RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va);
1312 va_end(va);
1313
1314 return false;
1315}
1316
1317
1318/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
1319
1320
1321/**
1322 * Processes a file.
1323 *
1324 * @returns IPRT status code.
1325 * @param pState The rewriter state.
1326 * @param pszFilename The file name.
1327 * @param pszBasename The base name (pointer within @a pszFilename).
1328 * @param cchBasename The length of the base name. (For passing to
1329 * RTStrSimplePatternMultiMatch.)
1330 * @param pBaseSettings The base settings to use. It's OK to modify
1331 * these.
1332 */
1333static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
1334 PSCMSETTINGSBASE pBaseSettings)
1335{
1336 /*
1337 * Do the file level filtering.
1338 */
1339 if ( pBaseSettings->pszFilterFiles
1340 && *pBaseSettings->pszFilterFiles
1341 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
1342 {
1343 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
1344 g_cFilesSkipped++;
1345 return VINF_SUCCESS;
1346 }
1347 if ( pBaseSettings->pszFilterOutFiles
1348 && *pBaseSettings->pszFilterOutFiles
1349 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
1350 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
1351 {
1352 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
1353 g_cFilesSkipped++;
1354 return VINF_SUCCESS;
1355 }
1356 if ( pBaseSettings->fOnlySvnFiles
1357 && !ScmSvnIsInWorkingCopy(pState))
1358 {
1359 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
1360 g_cFilesNotInSvn++;
1361 return VINF_SUCCESS;
1362 }
1363
1364 /*
1365 * Try find a matching rewrite config for this filename.
1366 */
1367 PCSCMCFGENTRY pCfg = NULL;
1368 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1369 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
1370 {
1371 pCfg = &g_aConfigs[iCfg];
1372 break;
1373 }
1374 if (!pCfg)
1375 {
1376 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
1377 g_cFilesNoRewriters++;
1378 return VINF_SUCCESS;
1379 }
1380 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
1381
1382 /*
1383 * Create an input stream from the file and check that it's text.
1384 */
1385 SCMSTREAM Stream1;
1386 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
1387 if (RT_FAILURE(rc))
1388 {
1389 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
1390 return rc;
1391 }
1392 if (ScmStreamIsText(&Stream1))
1393 {
1394 ScmVerboseBanner(pState, 3);
1395
1396 /*
1397 * Gather SCM and editor settings from the stream.
1398 */
1399 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
1400 if (RT_SUCCESS(rc))
1401 {
1402 ScmStreamRewindForReading(&Stream1);
1403
1404 /*
1405 * Create two more streams for output and push the text thru all the
1406 * rewriters, switching the two streams around when something is
1407 * actually rewritten. Stream1 remains unchanged.
1408 */
1409 SCMSTREAM Stream2;
1410 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
1411 if (RT_SUCCESS(rc))
1412 {
1413 SCMSTREAM Stream3;
1414 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
1415 if (RT_SUCCESS(rc))
1416 {
1417 bool fModified = false;
1418 PSCMSTREAM pIn = &Stream1;
1419 PSCMSTREAM pOut = &Stream2;
1420 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
1421 {
1422 pState->rc = VINF_SUCCESS;
1423 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
1424 if (RT_FAILURE(pState->rc))
1425 break;
1426 if (fRc)
1427 {
1428 PSCMSTREAM pTmp = pOut;
1429 pOut = pIn == &Stream1 ? &Stream3 : pIn;
1430 pIn = pTmp;
1431 fModified = true;
1432 }
1433
1434 ScmStreamRewindForReading(pIn);
1435 ScmStreamRewindForWriting(pOut);
1436 }
1437
1438 rc = pState->rc;
1439 if (RT_SUCCESS(rc))
1440 {
1441 rc = ScmStreamGetStatus(&Stream1);
1442 if (RT_SUCCESS(rc))
1443 rc = ScmStreamGetStatus(&Stream2);
1444 if (RT_SUCCESS(rc))
1445 rc = ScmStreamGetStatus(&Stream3);
1446 if (RT_SUCCESS(rc))
1447 {
1448 /*
1449 * If rewritten, write it back to disk.
1450 */
1451 if (fModified)
1452 {
1453 if (!g_fDryRun)
1454 {
1455 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1456 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
1457 if (RT_FAILURE(rc))
1458 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
1459 }
1460 else
1461 {
1462 ScmVerboseBanner(pState, 1);
1463 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol,
1464 g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars,
1465 pBaseSettings->cchTab, g_pStdOut);
1466 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n",
1467 pszFilename, g_pszChangedSuff);
1468 }
1469 g_cFilesModified++;
1470 }
1471
1472 /*
1473 * If pending SVN property changes, apply them.
1474 */
1475 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
1476 {
1477 if (!g_fDryRun)
1478 {
1479 rc = ScmSvnApplyChanges(pState);
1480 if (RT_FAILURE(rc))
1481 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
1482 }
1483 else
1484 ScmSvnDisplayChanges(pState);
1485 if (!fModified)
1486 g_cFilesModified++;
1487 }
1488
1489 if (!fModified && !pState->cSvnPropChanges)
1490 ScmVerbose(pState, 3, "%s: no change\n", pszFilename);
1491 }
1492 else
1493 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
1494 }
1495 ScmStreamDelete(&Stream3);
1496 }
1497 else
1498 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1499 ScmStreamDelete(&Stream2);
1500 }
1501 else
1502 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1503 }
1504 else
1505 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
1506 }
1507 else
1508 {
1509 ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename);
1510 g_cFilesBinaries++;
1511 }
1512 ScmStreamDelete(&Stream1);
1513
1514 return rc;
1515}
1516
1517/**
1518 * Processes a file.
1519 *
1520 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
1521 * directory recursion method.
1522 *
1523 * @returns IPRT status code.
1524 * @param pszFilename The file name.
1525 * @param pszBasename The base name (pointer within @a pszFilename).
1526 * @param cchBasename The length of the base name. (For passing to
1527 * RTStrSimplePatternMultiMatch.)
1528 * @param pSettingsStack The settings stack (pointer to the top element).
1529 */
1530static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
1531 PSCMSETTINGS pSettingsStack)
1532{
1533 SCMSETTINGSBASE Base;
1534 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
1535 if (RT_SUCCESS(rc))
1536 {
1537 SCMRWSTATE State;
1538 State.fFirst = false;
1539 State.pszFilename = pszFilename;
1540 State.cSvnPropChanges = 0;
1541 State.paSvnPropChanges = NULL;
1542 State.rc = VINF_SUCCESS;
1543
1544 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
1545
1546 size_t i = State.cSvnPropChanges;
1547 while (i-- > 0)
1548 {
1549 RTStrFree(State.paSvnPropChanges[i].pszName);
1550 RTStrFree(State.paSvnPropChanges[i].pszValue);
1551 }
1552 RTMemFree(State.paSvnPropChanges);
1553
1554 scmSettingsBaseDelete(&Base);
1555
1556 g_cFilesProcessed++;
1557 }
1558 return rc;
1559}
1560
1561
1562/**
1563 * Tries to correct RTDIRENTRY_UNKNOWN.
1564 *
1565 * @returns Corrected type.
1566 * @param pszPath The path to the object in question.
1567 */
1568static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
1569{
1570 RTFSOBJINFO Info;
1571 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
1572 if (RT_FAILURE(rc))
1573 return RTDIRENTRYTYPE_UNKNOWN;
1574 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
1575 return RTDIRENTRYTYPE_DIRECTORY;
1576 if (RTFS_IS_FILE(Info.Attr.fMode))
1577 return RTDIRENTRYTYPE_FILE;
1578 return RTDIRENTRYTYPE_UNKNOWN;
1579}
1580
1581/**
1582 * Recurse into a sub-directory and process all the files and directories.
1583 *
1584 * @returns IPRT status code.
1585 * @param pszBuf Path buffer containing the directory path on
1586 * entry. This ends with a dot. This is passed
1587 * along when recursing in order to save stack space
1588 * and avoid needless copying.
1589 * @param cchDir Length of our path in pszbuf.
1590 * @param pEntry Directory entry buffer. This is also passed
1591 * along when recursing to save stack space.
1592 * @param pSettingsStack The settings stack (pointer to the top element).
1593 * @param iRecursion The recursion depth. This is used to restrict
1594 * the recursions.
1595 */
1596static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
1597 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
1598{
1599 int rc;
1600 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
1601
1602 /*
1603 * Make sure we stop somewhere.
1604 */
1605 if (iRecursion > 128)
1606 {
1607 RTMsgError("recursion too deep: %d\n", iRecursion);
1608 return VINF_SUCCESS; /* ignore */
1609 }
1610
1611 /*
1612 * Check if it's excluded by --only-svn-dir.
1613 */
1614 if (pSettingsStack->Base.fOnlySvnDirs)
1615 {
1616 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
1617 return VINF_SUCCESS;
1618 }
1619 g_cDirsProcessed++;
1620
1621 /*
1622 * Try open and read the directory.
1623 */
1624 PRTDIR pDir;
1625 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
1626 if (RT_FAILURE(rc))
1627 {
1628 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
1629 return rc;
1630 }
1631 for (;;)
1632 {
1633 /* Read the next entry. */
1634 rc = RTDirRead(pDir, pEntry, NULL);
1635 if (RT_FAILURE(rc))
1636 {
1637 if (rc == VERR_NO_MORE_FILES)
1638 rc = VINF_SUCCESS;
1639 else
1640 RTMsgError("RTDirRead -> %Rrc\n", rc);
1641 break;
1642 }
1643
1644 /* Skip '.' and '..'. */
1645 if ( pEntry->szName[0] == '.'
1646 && ( pEntry->cbName == 1
1647 || ( pEntry->cbName == 2
1648 && pEntry->szName[1] == '.')))
1649 continue;
1650
1651 /* Enter it into the buffer so we've got a full name to work
1652 with when needed. */
1653 if (pEntry->cbName + cchDir >= RTPATH_MAX)
1654 {
1655 RTMsgError("Skipping too long entry: %s", pEntry->szName);
1656 continue;
1657 }
1658 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
1659
1660 /* Figure the type. */
1661 RTDIRENTRYTYPE enmType = pEntry->enmType;
1662 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
1663 enmType = scmFigureUnknownType(pszBuf);
1664
1665 /* Process the file or directory, skip the rest. */
1666 if (enmType == RTDIRENTRYTYPE_FILE)
1667 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
1668 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
1669 {
1670 /* Append the dot for the benefit of the pattern matching. */
1671 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
1672 {
1673 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
1674 continue;
1675 }
1676 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
1677 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
1678
1679 if ( !pSettingsStack->Base.pszFilterOutDirs
1680 || !*pSettingsStack->Base.pszFilterOutDirs
1681 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1682 pEntry->szName, pEntry->cbName, NULL)
1683 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1684 pszBuf, cchSubDir, NULL)
1685 )
1686 )
1687 {
1688 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
1689 if (RT_SUCCESS(rc))
1690 {
1691 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
1692 scmSettingsStackPopAndDestroy(&pSettingsStack);
1693 }
1694 }
1695 }
1696 if (RT_FAILURE(rc))
1697 break;
1698 }
1699 RTDirClose(pDir);
1700 return rc;
1701
1702}
1703
1704/**
1705 * Process a directory tree.
1706 *
1707 * @returns IPRT status code.
1708 * @param pszDir The directory to start with. This is pointer to
1709 * a RTPATH_MAX sized buffer.
1710 */
1711static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
1712{
1713 /*
1714 * Setup the recursion.
1715 */
1716 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
1717 if (RT_SUCCESS(rc))
1718 {
1719 RTPathChangeToUnixSlashes(pszDir, true);
1720
1721 RTDIRENTRY Entry;
1722 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
1723 }
1724 else
1725 RTMsgError("RTPathAppend: %Rrc\n", rc);
1726 return rc;
1727}
1728
1729
1730/**
1731 * Processes a file or directory specified as an command line argument.
1732 *
1733 * @returns IPRT status code
1734 * @param pszSomething What we found in the command line arguments.
1735 * @param pSettingsStack The settings stack (pointer to the top element).
1736 */
1737static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
1738{
1739 char szBuf[RTPATH_MAX];
1740 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
1741 if (RT_SUCCESS(rc))
1742 {
1743 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
1744
1745 PSCMSETTINGS pSettings;
1746 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
1747 if (RT_SUCCESS(rc))
1748 {
1749 scmSettingsStackPush(&pSettingsStack, pSettings);
1750
1751 if (RTFileExists(szBuf))
1752 {
1753 const char *pszBasename = RTPathFilename(szBuf);
1754 if (pszBasename)
1755 {
1756 size_t cchBasename = strlen(pszBasename);
1757 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
1758 }
1759 else
1760 {
1761 RTMsgError("RTPathFilename: NULL\n");
1762 rc = VERR_IS_A_DIRECTORY;
1763 }
1764 }
1765 else
1766 rc = scmProcessDirTree(szBuf, pSettingsStack);
1767
1768 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
1769 Assert(pPopped == pSettings); RT_NOREF_PV(pPopped);
1770 scmSettingsDestroy(pSettings);
1771 }
1772 else
1773 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
1774 }
1775 else
1776 RTMsgError("RTPathAbs: %Rrc\n", rc);
1777 return rc;
1778}
1779
1780/**
1781 * Print some stats.
1782 */
1783static void scmPrintStats(void)
1784{
1785 ScmVerbose(NULL, 0,
1786 g_fDryRun
1787 ? "%u out of %u file%s in %u dir%s would be modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n"
1788 : "%u out of %u file%s in %u dir%s was modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n",
1789 g_cFilesModified,
1790 g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s",
1791 g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s",
1792 g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s",
1793 g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies",
1794 g_cFilesNotInSvn, g_cFilesSkipped);
1795}
1796
1797static void usage(PCRTGETOPTDEF paOpts, size_t cOpts)
1798{
1799 RTPrintf("VirtualBox Source Code Massager\n"
1800 "\n"
1801 "Usage: %s [options] <files & dirs>\n"
1802 "\n"
1803 "Options:\n", g_szProgName);
1804 for (size_t i = 0; i < cOpts; i++)
1805 {
1806 size_t cExtraAdvance = 0;
1807 if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
1808 {
1809 cExtraAdvance = i + 1 < cOpts
1810 && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
1811 || strstr(paOpts[i+1].pszLong, "-not-") != NULL
1812 || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
1813 || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
1814 || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
1815 );
1816 if (cExtraAdvance)
1817 RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
1818 else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE)
1819 RTPrintf(" %s\n", paOpts[i].pszLong);
1820 else
1821 {
1822 RTPrintf(" %s,\n"
1823 " %s,\n"
1824 " %s,\n"
1825 " %s,\n"
1826 " %s\n",
1827 paOpts[i].pszLong,
1828 paOpts[i + 1].pszLong,
1829 paOpts[i + 2].pszLong,
1830 paOpts[i + 3].pszLong,
1831 paOpts[i + 4].pszLong);
1832 cExtraAdvance = 4;
1833 }
1834 }
1835 else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
1836 RTPrintf(" %s string\n", paOpts[i].pszLong);
1837 else
1838 RTPrintf(" %s value\n", paOpts[i].pszLong);
1839 switch (paOpts[i].iShort)
1840 {
1841 case 'd':
1842 case 'D': RTPrintf(" Default: --dry-run\n"); break;
1843 case 'f': RTPrintf(" Default: none\n"); break;
1844 case 'q':
1845 case 'v': RTPrintf(" Default: -vv\n"); break;
1846
1847 case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
1848 case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
1849 case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
1850 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
1851 case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
1852
1853 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
1854 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
1855 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
1856 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
1857 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
1858 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
1859 case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
1860 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
1861
1862 case SCMOPT_FIX_TODOS:
1863 RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos);
1864 break;
1865 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
1866 RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear);
1867 break;
1868 case SCMOPT_EXTERNAL_COPYRIGHT:
1869 RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright);
1870 break;
1871 case SCMOPT_NO_UPDATE_LICENSE:
1872 RTPrintf(" License selection. Default: --license-ose-gpl\n");
1873 break;
1874
1875 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
1876 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
1877 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
1878 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
1879 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
1880 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
1881 case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
1882 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
1883 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
1884 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
1885 default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
1886 }
1887 i += cExtraAdvance;
1888 }
1889
1890}
1891
1892int main(int argc, char **argv)
1893{
1894 int rc = RTR3InitExe(argc, &argv, 0);
1895 if (RT_FAILURE(rc))
1896 return 1;
1897
1898 /*
1899 * Init the current year.
1900 */
1901 RTTIMESPEC Now;
1902 RTTIME Time;
1903 RTTimeExplode(&Time, RTTimeNow(&Now));
1904 g_uYear = Time.i32Year;
1905
1906 /*
1907 * Init the settings.
1908 */
1909 PSCMSETTINGS pSettings;
1910 rc = scmSettingsCreate(&pSettings, &g_Defaults);
1911 if (RT_FAILURE(rc))
1912 {
1913 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
1914 return 1;
1915 }
1916
1917 /*
1918 * Parse arguments and process input in order (because this is the only
1919 * thing that works at the moment).
1920 */
1921 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
1922 {
1923 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1924 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
1925 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
1926 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
1927 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1928 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1929 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1930 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1931 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1932 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1933 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1934 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1935 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1936 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1937 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1938 };
1939 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
1940
1941 RTGETOPTUNION ValueUnion;
1942 RTGETOPTSTATE GetOptState;
1943 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1944 AssertReleaseRCReturn(rc, 1);
1945 size_t cProcessed = 0;
1946
1947 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1948 {
1949 switch (rc)
1950 {
1951 case 'd':
1952 g_fDryRun = true;
1953 break;
1954 case 'D':
1955 g_fDryRun = false;
1956 break;
1957
1958 case 'f':
1959 g_pszFileFilter = ValueUnion.psz;
1960 break;
1961
1962 case 'h':
1963 usage(s_aOpts, RT_ELEMENTS(s_aOpts));
1964 return 1;
1965
1966 case 'q':
1967 g_iVerbosity = 0;
1968 break;
1969
1970 case 'v':
1971 g_iVerbosity++;
1972 break;
1973
1974 case 'V':
1975 {
1976 /* The following is assuming that svn does it's job here. */
1977 static const char s_szRev[] = "$Revision: 69215 $";
1978 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
1979 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
1980 return 0;
1981 }
1982
1983 case SCMOPT_DIFF_IGNORE_EOL:
1984 g_fDiffIgnoreEol = true;
1985 break;
1986 case SCMOPT_DIFF_NO_IGNORE_EOL:
1987 g_fDiffIgnoreEol = false;
1988 break;
1989
1990 case SCMOPT_DIFF_IGNORE_SPACE:
1991 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
1992 break;
1993 case SCMOPT_DIFF_NO_IGNORE_SPACE:
1994 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
1995 break;
1996
1997 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
1998 g_fDiffIgnoreLeadingWS = true;
1999 break;
2000 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
2001 g_fDiffIgnoreLeadingWS = false;
2002 break;
2003
2004 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
2005 g_fDiffIgnoreTrailingWS = true;
2006 break;
2007 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
2008 g_fDiffIgnoreTrailingWS = false;
2009 break;
2010
2011 case SCMOPT_DIFF_SPECIAL_CHARS:
2012 g_fDiffSpecialChars = true;
2013 break;
2014 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
2015 g_fDiffSpecialChars = false;
2016 break;
2017
2018 case VINF_GETOPT_NOT_OPTION:
2019 {
2020 if (!g_fDryRun)
2021 {
2022 if (!cProcessed)
2023 {
2024 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
2025 "%s: there is a slight risk that bugs or a full disk may cause\n"
2026 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
2027 "%s: all your changes already. If you didn't, then don't blame\n"
2028 "%s: anyone for not warning you!\n"
2029 "%s:\n"
2030 "%s: Press any key to continue...\n",
2031 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
2032 g_szProgName, g_szProgName);
2033 RTStrmGetCh(g_pStdIn);
2034 }
2035 cProcessed++;
2036 }
2037 rc = scmProcessSomething(ValueUnion.psz, pSettings);
2038 if (RT_FAILURE(rc))
2039 {
2040 scmPrintStats();
2041 return RTEXITCODE_FAILURE;
2042 }
2043 break;
2044 }
2045
2046 default:
2047 {
2048 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1);
2049 if (RT_SUCCESS(rc2))
2050 break;
2051 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
2052 return 2;
2053 return RTGetOptPrintError(rc, &ValueUnion);
2054 }
2055 }
2056 }
2057
2058 scmPrintStats();
2059 scmSettingsDestroy(pSettings);
2060 return 0;
2061}
2062
Note: See TracBrowser for help on using the repository browser.

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