VirtualBox

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

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

scm: Relative filtering support. Zero file length allocation workaround.

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