VirtualBox

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

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

scm: Added --lgpl-disclaimer options (checking not yet implemented).

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

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