VirtualBox

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

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

scm: acpi sources

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