VirtualBox

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

Last change on this file since 69244 was 69244, checked in by vboxsync, 7 years ago

scm.cpp: warning fix

  • 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 69244 2017-10-24 18:36:08Z 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.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ctype.h>
24#include <iprt/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 for (;;)
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 /* not reached */
657 }
658
659 default:
660 return VERR_GETOPT_UNKNOWN_OPTION;
661 }
662}
663
664/**
665 * Parses an option string.
666 *
667 * @returns IPRT status code.
668 * @param pBase The base settings structure to apply the options
669 * to.
670 * @param pszOptions The options to parse.
671 * @param pchDir The absolute path to the directory relative
672 * components in pchLine should be relative to.
673 * @param cchDir The length of the @a pchDir string.
674 */
675static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir)
676{
677 int cArgs;
678 char **papszArgs;
679 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
680 if (RT_SUCCESS(rc))
681 {
682 RTGETOPTUNION ValueUnion;
683 RTGETOPTSTATE GetOptState;
684 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
685 if (RT_SUCCESS(rc))
686 {
687 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
688 {
689 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir);
690 if (RT_FAILURE(rc))
691 break;
692 }
693 }
694 RTGetOptArgvFree(papszArgs);
695 }
696
697 return rc;
698}
699
700/**
701 * Parses an unterminated option string.
702 *
703 * @returns IPRT status code.
704 * @param pBase The base settings structure to apply the options
705 * to.
706 * @param pchLine The line.
707 * @param cchLine The line length.
708 * @param pchDir The absolute path to the directory relative
709 * components in pchLine should be relative to.
710 * @param cchDir The length of the @a pchDir string.
711 */
712static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine,
713 const char *pchDir, size_t cchDir)
714{
715 char *pszLine = RTStrDupN(pchLine, cchLine);
716 if (!pszLine)
717 return VERR_NO_MEMORY;
718 int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir);
719 RTStrFree(pszLine);
720 return rc;
721}
722
723/**
724 * Verifies the options string.
725 *
726 * @returns IPRT status code.
727 * @param pszOptions The options to verify .
728 */
729static int scmSettingsBaseVerifyString(const char *pszOptions)
730{
731 SCMSETTINGSBASE Base;
732 int rc = scmSettingsBaseInit(&Base);
733 if (RT_SUCCESS(rc))
734 {
735 rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1);
736 scmSettingsBaseDelete(&Base);
737 }
738 return rc;
739}
740
741/**
742 * Loads settings found in editor and SCM settings directives within the
743 * document (@a pStream).
744 *
745 * @returns IPRT status code.
746 * @param pBase The settings base to load settings into.
747 * @param pStream The stream to scan for settings directives.
748 */
749static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
750{
751 /** @todo Editor and SCM settings directives in documents. */
752 RT_NOREF2(pBase, pStream);
753 return VINF_SUCCESS;
754}
755
756/**
757 * Creates a new settings file struct, cloning @a pSettings.
758 *
759 * @returns IPRT status code.
760 * @param ppSettings Where to return the new struct.
761 * @param pSettingsBase The settings to inherit from.
762 */
763static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
764{
765 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
766 if (!pSettings)
767 return VERR_NO_MEMORY;
768 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
769 if (RT_SUCCESS(rc))
770 {
771 pSettings->pDown = NULL;
772 pSettings->pUp = NULL;
773 pSettings->paPairs = NULL;
774 pSettings->cPairs = 0;
775 *ppSettings = pSettings;
776 return VINF_SUCCESS;
777 }
778 RTMemFree(pSettings);
779 return rc;
780}
781
782/**
783 * Destroys a settings structure.
784 *
785 * @param pSettings The settings structure to destroy. NULL is OK.
786 */
787static void scmSettingsDestroy(PSCMSETTINGS pSettings)
788{
789 if (pSettings)
790 {
791 scmSettingsBaseDelete(&pSettings->Base);
792 for (size_t i = 0; i < pSettings->cPairs; i++)
793 {
794 RTStrFree(pSettings->paPairs[i].pszPattern);
795 RTStrFree(pSettings->paPairs[i].pszOptions);
796 RTStrFree(pSettings->paPairs[i].pszRelativeTo);
797 pSettings->paPairs[i].pszPattern = NULL;
798 pSettings->paPairs[i].pszOptions = NULL;
799 pSettings->paPairs[i].pszRelativeTo = NULL;
800 }
801 RTMemFree(pSettings->paPairs);
802 pSettings->paPairs = NULL;
803 RTMemFree(pSettings);
804 }
805}
806
807/**
808 * Adds a pattern/options pair to the settings structure.
809 *
810 * @returns IPRT status code.
811 * @param pSettings The settings.
812 * @param pchLine The line containing the unparsed pair.
813 * @param cchLine The length of the line.
814 * @param offColon The offset of the colon into the line.
815 * @param pchDir The absolute path to the directory relative
816 * components in pchLine should be relative to.
817 * @param cchDir The length of the @a pchDir string.
818 */
819static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon,
820 const char *pchDir, size_t cchDir)
821{
822 Assert(pchLine[offColon] == ':' && offColon < cchLine);
823 Assert(pchDir[cchDir - 1] == '/');
824
825 /*
826 * Split the string.
827 */
828 size_t cchPattern = offColon;
829 size_t cchOptions = cchLine - cchPattern - 1;
830
831 /* strip spaces everywhere */
832 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
833 cchPattern--;
834 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
835 cchPattern--, pchLine++;
836
837 const char *pchOptions = &pchLine[offColon + 1];
838 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
839 cchOptions--;
840 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
841 cchOptions--, pchOptions++;
842
843 /* Quietly ignore empty patterns and empty options. */
844 if (!cchOptions || !cchPattern)
845 return VINF_SUCCESS;
846
847 /*
848 * Prepair the pair and verify the option string.
849 */
850 uint32_t iPair = pSettings->cPairs;
851 if ((iPair % 32) == 0)
852 {
853 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
854 if (!pvNew)
855 return VERR_NO_MEMORY;
856 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
857 }
858
859 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
860 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
861 pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir);
862 int rc;
863 if ( pSettings->paPairs[iPair].pszPattern
864 && pSettings->paPairs[iPair].pszOptions
865 && pSettings->paPairs[iPair].pszRelativeTo)
866 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
867 else
868 rc = VERR_NO_MEMORY;
869
870 /*
871 * If it checked out fine, expand any relative paths in the pattern.
872 */
873 if (RT_SUCCESS(rc))
874 {
875 size_t cRelativePaths = 0;
876 const char *pszSrc = pSettings->paPairs[iPair].pszPattern;
877 for (;;)
878 {
879 if (*pszSrc == '/')
880 cRelativePaths++;
881 pszSrc = strchr(pszSrc, '|');
882 if (!pszSrc)
883 break;
884 pszSrc++;
885 }
886 if (cRelativePaths > 0)
887 {
888 char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1);
889 if (pszNewPattern)
890 {
891 char *pszDst = pszNewPattern;
892 pszSrc = pSettings->paPairs[iPair].pszPattern;
893 for (;;)
894 {
895 if (*pszSrc == '/')
896 {
897 memcpy(pszDst, pchDir, cchDir);
898 pszDst += cchDir;
899 pszSrc += 1;
900 }
901
902 /* Look for the next relative path. */
903 const char *pszSrcNext = strchr(pszSrc, '|');
904 while (pszSrcNext && pszSrcNext[1] != '/')
905 pszSrcNext = strchr(pszSrcNext, '|');
906 if (!pszSrcNext)
907 break;
908
909 /* Copy stuff between current and the next path. */
910 pszSrcNext++;
911 memcpy(pszDst, pszSrc, pszSrcNext - pszSrc);
912 pszDst += pszSrcNext - pszSrc;
913 pszSrc = pszSrcNext;
914 }
915
916 /* Copy the final portion and replace the pattern. */
917 strcpy(pszDst, pszSrc);
918
919 RTStrFree(pSettings->paPairs[iPair].pszPattern);
920 pSettings->paPairs[iPair].pszPattern = pszNewPattern;
921 }
922 else
923 rc = VERR_NO_MEMORY;
924 }
925 }
926 if (RT_SUCCESS(rc))
927 /*
928 * Commit the pair.
929 */
930 pSettings->cPairs = iPair + 1;
931 else
932 {
933 RTStrFree(pSettings->paPairs[iPair].pszPattern);
934 RTStrFree(pSettings->paPairs[iPair].pszOptions);
935 RTStrFree(pSettings->paPairs[iPair].pszRelativeTo);
936 }
937 return rc;
938}
939
940/**
941 * Loads in the settings from @a pszFilename.
942 *
943 * @returns IPRT status code.
944 * @param pSettings Where to load the settings file.
945 * @param pszFilename The file to load. ASSUMED to be absolute.
946 * @param offName The offset of the name part in pszFilename.
947 */
948static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename, size_t offName)
949{
950 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
951
952#ifdef RT_STRICT
953 /* Check absolute path assumption. */
954 char szTmp[RTPATH_MAX];
955 int rcTmp = RTPathAbs(pszFilename, szTmp, sizeof(szTmp));
956 AssertMsg(RT_SUCCESS(rcTmp) && RTPathCompare(szTmp, pszFilename) == 0, ("rcTmp=%Rrc pszFilename='%s'\n", rcTmp, pszFilename));
957#endif
958
959 SCMSTREAM Stream;
960 int rc = ScmStreamInitForReading(&Stream, pszFilename);
961 if (RT_FAILURE(rc))
962 {
963 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
964 return rc;
965 }
966
967 SCMEOL enmEol;
968 const char *pchLine;
969 size_t cchLine;
970 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
971 {
972 /* Ignore leading spaces. */
973 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
974 pchLine++, cchLine--;
975
976 /* Ignore empty lines and comment lines. */
977 if (cchLine < 1 || *pchLine == '#')
978 continue;
979
980 /* What kind of line is it? */
981 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
982 if (pchColon)
983 rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, pszFilename, offName);
984 else
985 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, pszFilename, offName);
986 if (RT_FAILURE(rc))
987 {
988 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
989 break;
990 }
991 }
992
993 if (RT_SUCCESS(rc))
994 {
995 rc = ScmStreamGetStatus(&Stream);
996 if (RT_FAILURE(rc))
997 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
998 }
999
1000 ScmStreamDelete(&Stream);
1001 return rc;
1002}
1003
1004#if 0 /* unused */
1005/**
1006 * Parse the specified settings file creating a new settings struct from it.
1007 *
1008 * @returns IPRT status code
1009 * @param ppSettings Where to return the new settings.
1010 * @param pszFilename The file to parse.
1011 * @param pSettingsBase The base settings we inherit from.
1012 */
1013static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
1014{
1015 PSCMSETTINGS pSettings;
1016 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
1017 if (RT_SUCCESS(rc))
1018 {
1019 rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename);
1020 if (RT_SUCCESS(rc))
1021 {
1022 *ppSettings = pSettings;
1023 return VINF_SUCCESS;
1024 }
1025
1026 scmSettingsDestroy(pSettings);
1027 }
1028 *ppSettings = NULL;
1029 return rc;
1030}
1031#endif
1032
1033
1034/**
1035 * Create an initial settings structure when starting processing a new file or
1036 * directory.
1037 *
1038 * This will look for .scm-settings files from the root and down to the
1039 * specified directory, combining them into the returned settings structure.
1040 *
1041 * @returns IPRT status code.
1042 * @param ppSettings Where to return the pointer to the top stack
1043 * object.
1044 * @param pBaseSettings The base settings we inherit from (globals
1045 * typically).
1046 * @param pszPath The absolute path to the new directory or file.
1047 */
1048static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
1049{
1050 *ppSettings = NULL; /* try shut up gcc. */
1051
1052 /*
1053 * We'll be working with a stack copy of the path.
1054 */
1055 char szFile[RTPATH_MAX];
1056 size_t cchDir = strlen(pszPath);
1057 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
1058 return VERR_FILENAME_TOO_LONG;
1059
1060 /*
1061 * Create the bottom-most settings.
1062 */
1063 PSCMSETTINGS pSettings;
1064 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
1065 if (RT_FAILURE(rc))
1066 return rc;
1067
1068 /*
1069 * Enumerate the path components from the root and down. Load any setting
1070 * files we find.
1071 */
1072 size_t cComponents = RTPathCountComponents(pszPath);
1073 for (size_t i = 1; i <= cComponents; i++)
1074 {
1075 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
1076 if (RT_SUCCESS(rc))
1077 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
1078 if (RT_FAILURE(rc))
1079 break;
1080 RTPathChangeToUnixSlashes(szFile, true);
1081
1082 if (RTFileExists(szFile))
1083 {
1084 rc = scmSettingsLoadFile(pSettings, szFile, RTPathFilename(szFile) - &szFile[0]);
1085 if (RT_FAILURE(rc))
1086 break;
1087 }
1088 }
1089
1090 if (RT_SUCCESS(rc))
1091 *ppSettings = pSettings;
1092 else
1093 scmSettingsDestroy(pSettings);
1094 return rc;
1095}
1096
1097/**
1098 * Pushes a new settings set onto the stack.
1099 *
1100 * @param ppSettingsStack The pointer to the pointer to the top stack
1101 * element. This will be used as input and output.
1102 * @param pSettings The settings to push onto the stack.
1103 */
1104static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
1105{
1106 PSCMSETTINGS pOld = *ppSettingsStack;
1107 pSettings->pDown = pOld;
1108 pSettings->pUp = NULL;
1109 if (pOld)
1110 pOld->pUp = pSettings;
1111 *ppSettingsStack = pSettings;
1112}
1113
1114/**
1115 * Pushes the settings of the specified directory onto the stack.
1116 *
1117 * We will load any .scm-settings in the directory. A stack entry is added even
1118 * if no settings file was found.
1119 *
1120 * @returns IPRT status code.
1121 * @param ppSettingsStack The pointer to the pointer to the top stack
1122 * element. This will be used as input and output.
1123 * @param pszDir The directory to do this for.
1124 */
1125static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
1126{
1127 char szFile[RTPATH_MAX];
1128 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
1129 if (RT_SUCCESS(rc))
1130 {
1131 RTPathChangeToUnixSlashes(szFile, true);
1132
1133 PSCMSETTINGS pSettings;
1134 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
1135 if (RT_SUCCESS(rc))
1136 {
1137 if (RTFileExists(szFile))
1138 rc = scmSettingsLoadFile(pSettings, szFile, RTPathFilename(szFile) - &szFile[0]);
1139 if (RT_SUCCESS(rc))
1140 {
1141 scmSettingsStackPush(ppSettingsStack, pSettings);
1142 return VINF_SUCCESS;
1143 }
1144
1145 scmSettingsDestroy(pSettings);
1146 }
1147 }
1148 return rc;
1149}
1150
1151
1152/**
1153 * Pops a settings set off the stack.
1154 *
1155 * @returns The popped setttings.
1156 * @param ppSettingsStack The pointer to the pointer to the top stack
1157 * element. This will be used as input and output.
1158 */
1159static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
1160{
1161 PSCMSETTINGS pRet = *ppSettingsStack;
1162 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
1163 *ppSettingsStack = pNew;
1164 if (pNew)
1165 pNew->pUp = NULL;
1166 if (pRet)
1167 {
1168 pRet->pUp = NULL;
1169 pRet->pDown = NULL;
1170 }
1171 return pRet;
1172}
1173
1174/**
1175 * Pops and destroys the top entry of the stack.
1176 *
1177 * @param ppSettingsStack The pointer to the pointer to the top stack
1178 * element. This will be used as input and output.
1179 */
1180static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
1181{
1182 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
1183}
1184
1185/**
1186 * Constructs the base settings for the specified file name.
1187 *
1188 * @returns IPRT status code.
1189 * @param pSettingsStack The top element on the settings stack.
1190 * @param pszFilename The file name.
1191 * @param pszBasename The base name (pointer within @a pszFilename).
1192 * @param cchBasename The length of the base name. (For passing to
1193 * RTStrSimplePatternMultiMatch.)
1194 * @param pBase Base settings to initialize.
1195 */
1196static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
1197 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
1198{
1199 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename);
1200
1201 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
1202 if (RT_SUCCESS(rc))
1203 {
1204 /* find the bottom entry in the stack. */
1205 PCSCMSETTINGS pCur = pSettingsStack;
1206 while (pCur->pDown)
1207 pCur = pCur->pDown;
1208
1209 /* Work our way up thru the stack and look for matching pairs. */
1210 while (pCur)
1211 {
1212 size_t const cPairs = pCur->cPairs;
1213 if (cPairs)
1214 {
1215 for (size_t i = 0; i < cPairs; i++)
1216 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1217 pszBasename, cchBasename, NULL)
1218 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1219 pszFilename, RTSTR_MAX, NULL))
1220 {
1221 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n",
1222 pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions);
1223 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions,
1224 pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo));
1225 if (RT_FAILURE(rc))
1226 break;
1227 }
1228 if (RT_FAILURE(rc))
1229 break;
1230 }
1231
1232 /* advance */
1233 pCur = pCur->pUp;
1234 }
1235 }
1236 if (RT_FAILURE(rc))
1237 scmSettingsBaseDelete(pBase);
1238 return rc;
1239}
1240
1241
1242/* -=-=-=-=-=- misc -=-=-=-=-=- */
1243
1244
1245/**
1246 * Prints the per file banner needed and the message level is high enough.
1247 *
1248 * @param pState The rewrite state.
1249 * @param iLevel The required verbosity level.
1250 */
1251void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel)
1252{
1253 if (iLevel <= g_iVerbosity && !pState->fFirst)
1254 {
1255 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1256 pState->fFirst = true;
1257 }
1258}
1259
1260
1261/**
1262 * Prints a verbose message if the level is high enough.
1263 *
1264 * @param pState The rewrite state. Optional.
1265 * @param iLevel The required verbosity level.
1266 * @param pszFormat The message format string. Can be NULL if we
1267 * only want to trigger the per file message.
1268 * @param ... Format arguments.
1269 */
1270void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
1271{
1272 if (iLevel <= g_iVerbosity)
1273 {
1274 if (pState && !pState->fFirst)
1275 {
1276 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1277 pState->fFirst = true;
1278 }
1279 RTPrintf(pState
1280 ? "%s: info: "
1281 : "%s: info: ",
1282 g_szProgName);
1283 va_list va;
1284 va_start(va, pszFormat);
1285 RTPrintfV(pszFormat, va);
1286 va_end(va);
1287 }
1288}
1289
1290
1291/**
1292 * Prints an error message.
1293 *
1294 * @returns false
1295 * @param pState The rewrite state. Optional.
1296 * @param rc The error code.
1297 * @param pszFormat The message format string.
1298 * @param ... Format arguments.
1299 */
1300bool ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...)
1301{
1302 if (RT_SUCCESS(pState->rc))
1303 pState->rc = rc;
1304
1305 if (!pState->fFirst)
1306 {
1307 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1308 pState->fFirst = true;
1309 }
1310 va_list va;
1311 va_start(va, pszFormat);
1312 RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va);
1313 va_end(va);
1314
1315 return false;
1316}
1317
1318
1319/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
1320
1321
1322/**
1323 * Processes a file.
1324 *
1325 * @returns IPRT status code.
1326 * @param pState The rewriter state.
1327 * @param pszFilename The file name.
1328 * @param pszBasename The base name (pointer within @a pszFilename).
1329 * @param cchBasename The length of the base name. (For passing to
1330 * RTStrSimplePatternMultiMatch.)
1331 * @param pBaseSettings The base settings to use. It's OK to modify
1332 * these.
1333 */
1334static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
1335 PSCMSETTINGSBASE pBaseSettings)
1336{
1337 /*
1338 * Do the file level filtering.
1339 */
1340 if ( pBaseSettings->pszFilterFiles
1341 && *pBaseSettings->pszFilterFiles
1342 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
1343 {
1344 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
1345 g_cFilesSkipped++;
1346 return VINF_SUCCESS;
1347 }
1348 if ( pBaseSettings->pszFilterOutFiles
1349 && *pBaseSettings->pszFilterOutFiles
1350 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
1351 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
1352 {
1353 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
1354 g_cFilesSkipped++;
1355 return VINF_SUCCESS;
1356 }
1357 if ( pBaseSettings->fOnlySvnFiles
1358 && !ScmSvnIsInWorkingCopy(pState))
1359 {
1360 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
1361 g_cFilesNotInSvn++;
1362 return VINF_SUCCESS;
1363 }
1364
1365 /*
1366 * Try find a matching rewrite config for this filename.
1367 */
1368 PCSCMCFGENTRY pCfg = NULL;
1369 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1370 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
1371 {
1372 pCfg = &g_aConfigs[iCfg];
1373 break;
1374 }
1375 if (!pCfg)
1376 {
1377 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
1378 g_cFilesNoRewriters++;
1379 return VINF_SUCCESS;
1380 }
1381 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
1382
1383 /*
1384 * Create an input stream from the file and check that it's text.
1385 */
1386 SCMSTREAM Stream1;
1387 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
1388 if (RT_FAILURE(rc))
1389 {
1390 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
1391 return rc;
1392 }
1393 if (ScmStreamIsText(&Stream1))
1394 {
1395 ScmVerboseBanner(pState, 3);
1396
1397 /*
1398 * Gather SCM and editor settings from the stream.
1399 */
1400 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
1401 if (RT_SUCCESS(rc))
1402 {
1403 ScmStreamRewindForReading(&Stream1);
1404
1405 /*
1406 * Create two more streams for output and push the text thru all the
1407 * rewriters, switching the two streams around when something is
1408 * actually rewritten. Stream1 remains unchanged.
1409 */
1410 SCMSTREAM Stream2;
1411 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
1412 if (RT_SUCCESS(rc))
1413 {
1414 SCMSTREAM Stream3;
1415 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
1416 if (RT_SUCCESS(rc))
1417 {
1418 bool fModified = false;
1419 PSCMSTREAM pIn = &Stream1;
1420 PSCMSTREAM pOut = &Stream2;
1421 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
1422 {
1423 pState->rc = VINF_SUCCESS;
1424 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
1425 if (RT_FAILURE(pState->rc))
1426 break;
1427 if (fRc)
1428 {
1429 PSCMSTREAM pTmp = pOut;
1430 pOut = pIn == &Stream1 ? &Stream3 : pIn;
1431 pIn = pTmp;
1432 fModified = true;
1433 }
1434
1435 ScmStreamRewindForReading(pIn);
1436 ScmStreamRewindForWriting(pOut);
1437 }
1438
1439 rc = pState->rc;
1440 if (RT_SUCCESS(rc))
1441 {
1442 rc = ScmStreamGetStatus(&Stream1);
1443 if (RT_SUCCESS(rc))
1444 rc = ScmStreamGetStatus(&Stream2);
1445 if (RT_SUCCESS(rc))
1446 rc = ScmStreamGetStatus(&Stream3);
1447 if (RT_SUCCESS(rc))
1448 {
1449 /*
1450 * If rewritten, write it back to disk.
1451 */
1452 if (fModified)
1453 {
1454 if (!g_fDryRun)
1455 {
1456 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1457 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
1458 if (RT_FAILURE(rc))
1459 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
1460 }
1461 else
1462 {
1463 ScmVerboseBanner(pState, 1);
1464 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol,
1465 g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars,
1466 pBaseSettings->cchTab, g_pStdOut);
1467 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n",
1468 pszFilename, g_pszChangedSuff);
1469 }
1470 g_cFilesModified++;
1471 }
1472
1473 /*
1474 * If pending SVN property changes, apply them.
1475 */
1476 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
1477 {
1478 if (!g_fDryRun)
1479 {
1480 rc = ScmSvnApplyChanges(pState);
1481 if (RT_FAILURE(rc))
1482 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
1483 }
1484 else
1485 ScmSvnDisplayChanges(pState);
1486 if (!fModified)
1487 g_cFilesModified++;
1488 }
1489
1490 if (!fModified && !pState->cSvnPropChanges)
1491 ScmVerbose(pState, 3, "%s: no change\n", pszFilename);
1492 }
1493 else
1494 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
1495 }
1496 ScmStreamDelete(&Stream3);
1497 }
1498 else
1499 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1500 ScmStreamDelete(&Stream2);
1501 }
1502 else
1503 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1504 }
1505 else
1506 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
1507 }
1508 else
1509 {
1510 ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename);
1511 g_cFilesBinaries++;
1512 }
1513 ScmStreamDelete(&Stream1);
1514
1515 return rc;
1516}
1517
1518/**
1519 * Processes a file.
1520 *
1521 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
1522 * directory recursion method.
1523 *
1524 * @returns IPRT status code.
1525 * @param pszFilename The file name.
1526 * @param pszBasename The base name (pointer within @a pszFilename).
1527 * @param cchBasename The length of the base name. (For passing to
1528 * RTStrSimplePatternMultiMatch.)
1529 * @param pSettingsStack The settings stack (pointer to the top element).
1530 */
1531static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
1532 PSCMSETTINGS pSettingsStack)
1533{
1534 SCMSETTINGSBASE Base;
1535 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
1536 if (RT_SUCCESS(rc))
1537 {
1538 SCMRWSTATE State;
1539 State.fFirst = false;
1540 State.pszFilename = pszFilename;
1541 State.cSvnPropChanges = 0;
1542 State.paSvnPropChanges = NULL;
1543 State.rc = VINF_SUCCESS;
1544
1545 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
1546
1547 size_t i = State.cSvnPropChanges;
1548 while (i-- > 0)
1549 {
1550 RTStrFree(State.paSvnPropChanges[i].pszName);
1551 RTStrFree(State.paSvnPropChanges[i].pszValue);
1552 }
1553 RTMemFree(State.paSvnPropChanges);
1554
1555 scmSettingsBaseDelete(&Base);
1556
1557 g_cFilesProcessed++;
1558 }
1559 return rc;
1560}
1561
1562
1563/**
1564 * Tries to correct RTDIRENTRY_UNKNOWN.
1565 *
1566 * @returns Corrected type.
1567 * @param pszPath The path to the object in question.
1568 */
1569static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
1570{
1571 RTFSOBJINFO Info;
1572 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
1573 if (RT_FAILURE(rc))
1574 return RTDIRENTRYTYPE_UNKNOWN;
1575 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
1576 return RTDIRENTRYTYPE_DIRECTORY;
1577 if (RTFS_IS_FILE(Info.Attr.fMode))
1578 return RTDIRENTRYTYPE_FILE;
1579 return RTDIRENTRYTYPE_UNKNOWN;
1580}
1581
1582/**
1583 * Recurse into a sub-directory and process all the files and directories.
1584 *
1585 * @returns IPRT status code.
1586 * @param pszBuf Path buffer containing the directory path on
1587 * entry. This ends with a dot. This is passed
1588 * along when recursing in order to save stack space
1589 * and avoid needless copying.
1590 * @param cchDir Length of our path in pszbuf.
1591 * @param pEntry Directory entry buffer. This is also passed
1592 * along when recursing to save stack space.
1593 * @param pSettingsStack The settings stack (pointer to the top element).
1594 * @param iRecursion The recursion depth. This is used to restrict
1595 * the recursions.
1596 */
1597static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
1598 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
1599{
1600 int rc;
1601 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
1602
1603 /*
1604 * Make sure we stop somewhere.
1605 */
1606 if (iRecursion > 128)
1607 {
1608 RTMsgError("recursion too deep: %d\n", iRecursion);
1609 return VINF_SUCCESS; /* ignore */
1610 }
1611
1612 /*
1613 * Check if it's excluded by --only-svn-dir.
1614 */
1615 if (pSettingsStack->Base.fOnlySvnDirs)
1616 {
1617 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
1618 return VINF_SUCCESS;
1619 }
1620 g_cDirsProcessed++;
1621
1622 /*
1623 * Try open and read the directory.
1624 */
1625 PRTDIR pDir;
1626 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
1627 if (RT_FAILURE(rc))
1628 {
1629 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
1630 return rc;
1631 }
1632 for (;;)
1633 {
1634 /* Read the next entry. */
1635 rc = RTDirRead(pDir, pEntry, NULL);
1636 if (RT_FAILURE(rc))
1637 {
1638 if (rc == VERR_NO_MORE_FILES)
1639 rc = VINF_SUCCESS;
1640 else
1641 RTMsgError("RTDirRead -> %Rrc\n", rc);
1642 break;
1643 }
1644
1645 /* Skip '.' and '..'. */
1646 if ( pEntry->szName[0] == '.'
1647 && ( pEntry->cbName == 1
1648 || ( pEntry->cbName == 2
1649 && pEntry->szName[1] == '.')))
1650 continue;
1651
1652 /* Enter it into the buffer so we've got a full name to work
1653 with when needed. */
1654 if (pEntry->cbName + cchDir >= RTPATH_MAX)
1655 {
1656 RTMsgError("Skipping too long entry: %s", pEntry->szName);
1657 continue;
1658 }
1659 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
1660
1661 /* Figure the type. */
1662 RTDIRENTRYTYPE enmType = pEntry->enmType;
1663 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
1664 enmType = scmFigureUnknownType(pszBuf);
1665
1666 /* Process the file or directory, skip the rest. */
1667 if (enmType == RTDIRENTRYTYPE_FILE)
1668 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
1669 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
1670 {
1671 /* Append the dot for the benefit of the pattern matching. */
1672 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
1673 {
1674 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
1675 continue;
1676 }
1677 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
1678 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
1679
1680 if ( !pSettingsStack->Base.pszFilterOutDirs
1681 || !*pSettingsStack->Base.pszFilterOutDirs
1682 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1683 pEntry->szName, pEntry->cbName, NULL)
1684 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1685 pszBuf, cchSubDir, NULL)
1686 )
1687 )
1688 {
1689 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
1690 if (RT_SUCCESS(rc))
1691 {
1692 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
1693 scmSettingsStackPopAndDestroy(&pSettingsStack);
1694 }
1695 }
1696 }
1697 if (RT_FAILURE(rc))
1698 break;
1699 }
1700 RTDirClose(pDir);
1701 return rc;
1702
1703}
1704
1705/**
1706 * Process a directory tree.
1707 *
1708 * @returns IPRT status code.
1709 * @param pszDir The directory to start with. This is pointer to
1710 * a RTPATH_MAX sized buffer.
1711 */
1712static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
1713{
1714 /*
1715 * Setup the recursion.
1716 */
1717 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
1718 if (RT_SUCCESS(rc))
1719 {
1720 RTPathChangeToUnixSlashes(pszDir, true);
1721
1722 RTDIRENTRY Entry;
1723 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
1724 }
1725 else
1726 RTMsgError("RTPathAppend: %Rrc\n", rc);
1727 return rc;
1728}
1729
1730
1731/**
1732 * Processes a file or directory specified as an command line argument.
1733 *
1734 * @returns IPRT status code
1735 * @param pszSomething What we found in the command line arguments.
1736 * @param pSettingsStack The settings stack (pointer to the top element).
1737 */
1738static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
1739{
1740 char szBuf[RTPATH_MAX];
1741 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
1742 if (RT_SUCCESS(rc))
1743 {
1744 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
1745
1746 PSCMSETTINGS pSettings;
1747 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
1748 if (RT_SUCCESS(rc))
1749 {
1750 scmSettingsStackPush(&pSettingsStack, pSettings);
1751
1752 if (RTFileExists(szBuf))
1753 {
1754 const char *pszBasename = RTPathFilename(szBuf);
1755 if (pszBasename)
1756 {
1757 size_t cchBasename = strlen(pszBasename);
1758 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
1759 }
1760 else
1761 {
1762 RTMsgError("RTPathFilename: NULL\n");
1763 rc = VERR_IS_A_DIRECTORY;
1764 }
1765 }
1766 else
1767 rc = scmProcessDirTree(szBuf, pSettingsStack);
1768
1769 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
1770 Assert(pPopped == pSettings); RT_NOREF_PV(pPopped);
1771 scmSettingsDestroy(pSettings);
1772 }
1773 else
1774 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
1775 }
1776 else
1777 RTMsgError("RTPathAbs: %Rrc\n", rc);
1778 return rc;
1779}
1780
1781/**
1782 * Print some stats.
1783 */
1784static void scmPrintStats(void)
1785{
1786 ScmVerbose(NULL, 0,
1787 g_fDryRun
1788 ? "%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"
1789 : "%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",
1790 g_cFilesModified,
1791 g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s",
1792 g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s",
1793 g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s",
1794 g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies",
1795 g_cFilesNotInSvn, g_cFilesSkipped);
1796}
1797
1798static void usage(PCRTGETOPTDEF paOpts, size_t cOpts)
1799{
1800 RTPrintf("VirtualBox Source Code Massager\n"
1801 "\n"
1802 "Usage: %s [options] <files & dirs>\n"
1803 "\n"
1804 "Options:\n", g_szProgName);
1805 for (size_t i = 0; i < cOpts; i++)
1806 {
1807 size_t cExtraAdvance = 0;
1808 if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
1809 {
1810 cExtraAdvance = i + 1 < cOpts
1811 && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
1812 || strstr(paOpts[i+1].pszLong, "-not-") != NULL
1813 || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
1814 || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
1815 || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
1816 );
1817 if (cExtraAdvance)
1818 RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
1819 else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE)
1820 RTPrintf(" %s\n", paOpts[i].pszLong);
1821 else
1822 {
1823 RTPrintf(" %s,\n"
1824 " %s,\n"
1825 " %s,\n"
1826 " %s,\n"
1827 " %s\n",
1828 paOpts[i].pszLong,
1829 paOpts[i + 1].pszLong,
1830 paOpts[i + 2].pszLong,
1831 paOpts[i + 3].pszLong,
1832 paOpts[i + 4].pszLong);
1833 cExtraAdvance = 4;
1834 }
1835 }
1836 else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
1837 RTPrintf(" %s string\n", paOpts[i].pszLong);
1838 else
1839 RTPrintf(" %s value\n", paOpts[i].pszLong);
1840 switch (paOpts[i].iShort)
1841 {
1842 case 'd':
1843 case 'D': RTPrintf(" Default: --dry-run\n"); break;
1844 case 'f': RTPrintf(" Default: none\n"); break;
1845 case 'q':
1846 case 'v': RTPrintf(" Default: -vv\n"); break;
1847
1848 case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
1849 case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
1850 case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
1851 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
1852 case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
1853
1854 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
1855 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
1856 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
1857 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
1858 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
1859 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
1860 case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
1861 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
1862
1863 case SCMOPT_FIX_TODOS:
1864 RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos);
1865 break;
1866 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
1867 RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear);
1868 break;
1869 case SCMOPT_EXTERNAL_COPYRIGHT:
1870 RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright);
1871 break;
1872 case SCMOPT_NO_UPDATE_LICENSE:
1873 RTPrintf(" License selection. Default: --license-ose-gpl\n");
1874 break;
1875
1876 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
1877 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
1878 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
1879 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
1880 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
1881 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
1882 case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
1883 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
1884 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
1885 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
1886 default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
1887 }
1888 i += cExtraAdvance;
1889 }
1890
1891}
1892
1893int main(int argc, char **argv)
1894{
1895 int rc = RTR3InitExe(argc, &argv, 0);
1896 if (RT_FAILURE(rc))
1897 return 1;
1898
1899 /*
1900 * Init the current year.
1901 */
1902 RTTIMESPEC Now;
1903 RTTIME Time;
1904 RTTimeExplode(&Time, RTTimeNow(&Now));
1905 g_uYear = Time.i32Year;
1906
1907 /*
1908 * Init the settings.
1909 */
1910 PSCMSETTINGS pSettings;
1911 rc = scmSettingsCreate(&pSettings, &g_Defaults);
1912 if (RT_FAILURE(rc))
1913 {
1914 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
1915 return 1;
1916 }
1917
1918 /*
1919 * Parse arguments and process input in order (because this is the only
1920 * thing that works at the moment).
1921 */
1922 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
1923 {
1924 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1925 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
1926 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
1927 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
1928 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1929 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1930 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1931 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1932 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1933 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1934 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1935 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1936 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1937 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1938 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1939 };
1940 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
1941
1942 RTGETOPTUNION ValueUnion;
1943 RTGETOPTSTATE GetOptState;
1944 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1945 AssertReleaseRCReturn(rc, 1);
1946 size_t cProcessed = 0;
1947
1948 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1949 {
1950 switch (rc)
1951 {
1952 case 'd':
1953 g_fDryRun = true;
1954 break;
1955 case 'D':
1956 g_fDryRun = false;
1957 break;
1958
1959 case 'f':
1960 g_pszFileFilter = ValueUnion.psz;
1961 break;
1962
1963 case 'h':
1964 usage(s_aOpts, RT_ELEMENTS(s_aOpts));
1965 return 1;
1966
1967 case 'q':
1968 g_iVerbosity = 0;
1969 break;
1970
1971 case 'v':
1972 g_iVerbosity++;
1973 break;
1974
1975 case 'V':
1976 {
1977 /* The following is assuming that svn does it's job here. */
1978 static const char s_szRev[] = "$Revision: 69244 $";
1979 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
1980 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
1981 return 0;
1982 }
1983
1984 case SCMOPT_DIFF_IGNORE_EOL:
1985 g_fDiffIgnoreEol = true;
1986 break;
1987 case SCMOPT_DIFF_NO_IGNORE_EOL:
1988 g_fDiffIgnoreEol = false;
1989 break;
1990
1991 case SCMOPT_DIFF_IGNORE_SPACE:
1992 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
1993 break;
1994 case SCMOPT_DIFF_NO_IGNORE_SPACE:
1995 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
1996 break;
1997
1998 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
1999 g_fDiffIgnoreLeadingWS = true;
2000 break;
2001 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
2002 g_fDiffIgnoreLeadingWS = false;
2003 break;
2004
2005 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
2006 g_fDiffIgnoreTrailingWS = true;
2007 break;
2008 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
2009 g_fDiffIgnoreTrailingWS = false;
2010 break;
2011
2012 case SCMOPT_DIFF_SPECIAL_CHARS:
2013 g_fDiffSpecialChars = true;
2014 break;
2015 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
2016 g_fDiffSpecialChars = false;
2017 break;
2018
2019 case VINF_GETOPT_NOT_OPTION:
2020 {
2021 if (!g_fDryRun)
2022 {
2023 if (!cProcessed)
2024 {
2025 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
2026 "%s: there is a slight risk that bugs or a full disk may cause\n"
2027 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
2028 "%s: all your changes already. If you didn't, then don't blame\n"
2029 "%s: anyone for not warning you!\n"
2030 "%s:\n"
2031 "%s: Press any key to continue...\n",
2032 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
2033 g_szProgName, g_szProgName);
2034 RTStrmGetCh(g_pStdIn);
2035 }
2036 cProcessed++;
2037 }
2038 rc = scmProcessSomething(ValueUnion.psz, pSettings);
2039 if (RT_FAILURE(rc))
2040 {
2041 scmPrintStats();
2042 return RTEXITCODE_FAILURE;
2043 }
2044 break;
2045 }
2046
2047 default:
2048 {
2049 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1);
2050 if (RT_SUCCESS(rc2))
2051 break;
2052 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
2053 return 2;
2054 return RTGetOptPrintError(rc, &ValueUnion);
2055 }
2056 }
2057 }
2058
2059 scmPrintStats();
2060 scmSettingsDestroy(pSettings);
2061 return 0;
2062}
2063
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