VirtualBox

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

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

scm: fix up non-kBuild makefiles too

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