VirtualBox

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

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

scm: a couple of optimizations. (fIsInSvnWorkingCopy is the one that is paying off)

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