VirtualBox

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

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

scm: rewrite .pro files

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