VirtualBox

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

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

scm: *.inc files are also assembly files

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