VirtualBox

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

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

scm: .pm files are perl

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