VirtualBox

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

Last change on this file since 74071 was 72568, checked in by vboxsync, 6 years ago

scm: Use RTMemDupEx in scmCfgEntryDup so we don't read beyond the end of static rewriter configs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 108.5 KB
Line 
1/* $Id: scm.cpp 72568 2018-06-15 15:18:34Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2017 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_SKIP_SVN_SYNC_PROCESS,
104 SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS,
105 SCMOPT_TAB_SIZE,
106 SCMOPT_WIDTH,
107 SCMOPT_FILTER_OUT_DIRS,
108 SCMOPT_FILTER_FILES,
109 SCMOPT_FILTER_OUT_FILES,
110 SCMOPT_TREAT_AS,
111 SCMOPT_ADD_ACTION,
112 SCMOPT_DEL_ACTION,
113 SCMOPT_LAST_SETTINGS = SCMOPT_DEL_ACTION,
114 //
115 SCMOPT_CHECK_RUN,
116 SCMOPT_DIFF_IGNORE_EOL,
117 SCMOPT_DIFF_NO_IGNORE_EOL,
118 SCMOPT_DIFF_IGNORE_SPACE,
119 SCMOPT_DIFF_NO_IGNORE_SPACE,
120 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
121 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
122 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
123 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
124 SCMOPT_DIFF_SPECIAL_CHARS,
125 SCMOPT_DIFF_NO_SPECIAL_CHARS,
126 SCMOPT_HELP_CONFIG,
127 SCMOPT_HELP_ACTIONS,
128 SCMOPT_END
129} SCMOPT;
130
131
132/*********************************************************************************************************************************
133* Global Variables *
134*********************************************************************************************************************************/
135const char g_szTabSpaces[16+1] = " ";
136const char g_szAsterisks[255+1] =
137"****************************************************************************************************"
138"****************************************************************************************************"
139"*******************************************************";
140const char g_szSpaces[255+1] =
141" "
142" "
143" ";
144static const char g_szProgName[] = "scm";
145static const char *g_pszChangedSuff = "";
146static bool g_fDryRun = true;
147static bool g_fDiffSpecialChars = true;
148static bool g_fDiffIgnoreEol = false;
149static bool g_fDiffIgnoreLeadingWS = false;
150static bool g_fDiffIgnoreTrailingWS = false;
151static int g_iVerbosity = 2;//99; //0;
152uint32_t g_uYear = 0; /**< The current year. */
153/** @name Statistics
154 * @{ */
155static uint32_t g_cDirsProcessed = 0;
156static uint32_t g_cFilesProcessed = 0;
157static uint32_t g_cFilesModified = 0;
158static uint32_t g_cFilesSkipped = 0;
159static uint32_t g_cFilesNotInSvn = 0;
160static uint32_t g_cFilesNoRewriters = 0;
161static uint32_t g_cFilesBinaries = 0;
162/** @} */
163
164/** The global settings. */
165static SCMSETTINGSBASE const g_Defaults =
166{
167 /* .fConvertEol = */ true,
168 /* .fConvertTabs = */ true,
169 /* .fForceFinalEol = */ true,
170 /* .fForceTrailingLine = */ false,
171 /* .fStripTrailingBlanks = */ true,
172 /* .fStripTrailingLines = */ true,
173 /* .fFixFlowerBoxMarkers = */ true,
174 /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2,
175 /* .fFixTodos = */ true,
176 /* .fUpdateCopyrightYear = */ false,
177 /* .fExternalCopyright = */ false,
178 /* .fLgplDisclaimer = */ false,
179 /* .enmUpdateLicense = */ kScmLicense_OseGpl,
180 /* .fOnlySvnFiles = */ false,
181 /* .fOnlySvnDirs = */ false,
182 /* .fSetSvnEol = */ false,
183 /* .fSetSvnExecutable = */ false,
184 /* .fSetSvnKeywords = */ false,
185 /* .fSkipSvnSyncProcess = */ false,
186 /* .cchTab = */ 8,
187 /* .cchWidth = */ 130,
188 /* .fFreeTreatAs = */ false,
189 /* .pTreatAs = */ NULL,
190 /* .pszFilterFiles = */ (char *)"",
191 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
192 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
193};
194
195/** Option definitions for the base settings. */
196static RTGETOPTDEF g_aScmOpts[] =
197{
198 /* rewriters */
199 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
200 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
201 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
202 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
203 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
204 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
205 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
206 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
207 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
208 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
209 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
210 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
211 { "--min-blank-lines-before-flower-box-makers", SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 },
212 { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
213 { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
214 { "--fix-todos", SCMOPT_FIX_TODOS, RTGETOPT_REQ_NOTHING },
215 { "--no-fix-todos", SCMOPT_NO_FIX_TODOS, RTGETOPT_REQ_NOTHING },
216 { "--update-copyright-year", SCMOPT_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
217 { "--no-update-copyright-year", SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
218 { "--external-copyright", SCMOPT_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
219 { "--no-external-copyright", SCMOPT_NO_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
220 { "--no-update-license", SCMOPT_NO_UPDATE_LICENSE, RTGETOPT_REQ_NOTHING },
221 { "--license-ose-gpl", SCMOPT_LICENSE_OSE_GPL, RTGETOPT_REQ_NOTHING },
222 { "--license-ose-dual", SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, RTGETOPT_REQ_NOTHING },
223 { "--license-ose-cddl", SCMOPT_LICENSE_OSE_CDDL, RTGETOPT_REQ_NOTHING },
224 { "--license-lgpl", SCMOPT_LICENSE_LGPL, RTGETOPT_REQ_NOTHING },
225 { "--license-mit", SCMOPT_LICENSE_MIT, RTGETOPT_REQ_NOTHING },
226 { "--license-based-on-mit", SCMOPT_LICENSE_BASED_ON_MIT, RTGETOPT_REQ_NOTHING },
227 { "--lgpl-disclaimer", SCMOPT_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
228 { "--no-lgpl-disclaimer", SCMOPT_NO_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
229 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
230 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
231 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
232 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
233 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
234 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
235 { "--skip-svn-sync-process", SCMOPT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
236 { "--dont-skip-svn-sync-process", SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
237 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
238 { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 },
239
240 /* input selection */
241 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
242 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
243 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
244 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
245 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
246 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
247 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
248
249 /* rewriter selection */
250 { "--treat-as", SCMOPT_TREAT_AS, RTGETOPT_REQ_STRING },
251 { "--add-action", SCMOPT_ADD_ACTION, RTGETOPT_REQ_STRING },
252 { "--del-action", SCMOPT_DEL_ACTION, RTGETOPT_REQ_STRING },
253
254 /* Additional help */
255 { "--help-config", SCMOPT_HELP_CONFIG, RTGETOPT_REQ_NOTHING },
256 { "--help-actions", SCMOPT_HELP_ACTIONS, RTGETOPT_REQ_NOTHING },
257};
258
259/** Consider files matching the following patterns (base names only). */
260static const char *g_pszFileFilter = NULL;
261
262/* The rewriter configuration. */
263#define SCM_REWRITER_CFG(a_Global, a_szName, fnRewriter) static const SCMREWRITERCFG a_Global = { &fnRewriter, a_szName }
264SCM_REWRITER_CFG(g_StripTrailingBlanks, "strip-trailing-blanks", rewrite_StripTrailingBlanks);
265SCM_REWRITER_CFG(g_ExpandTabs, "expand-tabs", rewrite_ExpandTabs);
266SCM_REWRITER_CFG(g_ForceNativeEol, "force-native-eol", rewrite_ForceNativeEol);
267SCM_REWRITER_CFG(g_ForceLF, "force-lf", rewrite_ForceLF);
268SCM_REWRITER_CFG(g_ForceCRLF, "force-crlf", rewrite_ForceCRLF);
269SCM_REWRITER_CFG(g_AdjustTrailingLines, "adjust-trailing-lines", rewrite_AdjustTrailingLines);
270SCM_REWRITER_CFG(g_SvnNoExecutable, "svn-no-executable", rewrite_SvnNoExecutable);
271SCM_REWRITER_CFG(g_SvnNoKeywords, "svn-no-keywords", rewrite_SvnNoKeywords);
272SCM_REWRITER_CFG(g_SvnNoEolStyle, "svn-no-eol-style", rewrite_SvnNoEolStyle);
273SCM_REWRITER_CFG(g_SvnBinary, "svn-binary", rewrite_SvnBinary);
274SCM_REWRITER_CFG(g_SvnKeywords, "svn-keywords", rewrite_SvnKeywords);
275SCM_REWRITER_CFG(g_SvnSyncProcess, "svn-sync-process", rewrite_SvnSyncProcess);
276SCM_REWRITER_CFG(g_Copyright_CstyleComment, "copyright-c-style", rewrite_Copyright_CstyleComment);
277SCM_REWRITER_CFG(g_Copyright_HashComment, "copyright-hash-style", rewrite_Copyright_HashComment);
278SCM_REWRITER_CFG(g_Copyright_PythonComment, "copyright-python-style", rewrite_Copyright_PythonComment);
279SCM_REWRITER_CFG(g_Copyright_RemComment, "copyright-rem-style", rewrite_Copyright_RemComment);
280SCM_REWRITER_CFG(g_Copyright_SemicolonComment, "copyright-semicolon-style", rewrite_Copyright_SemicolonComment);
281SCM_REWRITER_CFG(g_Copyright_SqlComment, "copyright-sql-style", rewrite_Copyright_SqlComment);
282SCM_REWRITER_CFG(g_Copyright_TickComment, "copyright-tick-style", rewrite_Copyright_TickComment);
283SCM_REWRITER_CFG(g_Makefile_kup, "makefile-kup", rewrite_Makefile_kup);
284SCM_REWRITER_CFG(g_Makefile_kmk, "makefile-kmk", rewrite_Makefile_kmk);
285SCM_REWRITER_CFG(g_FixFlowerBoxMarkers, "fix-flower-boxes", rewrite_FixFlowerBoxMarkers);
286SCM_REWRITER_CFG(g_Fix_C_and_CPP_Todos, "fix-c-todos", rewrite_Fix_C_and_CPP_Todos);
287SCM_REWRITER_CFG(g_C_and_CPP, "c-and-cpp", rewrite_C_and_CPP);
288
289/** The rewriter actions. */
290static PCSCMREWRITERCFG const g_papRewriterActions[] =
291{
292 &g_StripTrailingBlanks,
293 &g_ExpandTabs,
294 &g_ForceNativeEol,
295 &g_ForceLF,
296 &g_ForceCRLF,
297 &g_AdjustTrailingLines,
298 &g_SvnNoExecutable,
299 &g_SvnNoKeywords,
300 &g_SvnNoEolStyle,
301 &g_SvnBinary,
302 &g_SvnKeywords,
303 &g_SvnSyncProcess,
304 &g_Copyright_CstyleComment,
305 &g_Copyright_HashComment,
306 &g_Copyright_PythonComment,
307 &g_Copyright_RemComment,
308 &g_Copyright_SemicolonComment,
309 &g_Copyright_SqlComment,
310 &g_Copyright_TickComment,
311 &g_Makefile_kup,
312 &g_Makefile_kmk,
313 &g_FixFlowerBoxMarkers,
314 &g_Fix_C_and_CPP_Todos,
315 &g_C_and_CPP,
316};
317
318
319static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kup[] =
320{
321 &g_SvnNoExecutable,
322 &g_SvnSyncProcess,
323 &g_Makefile_kup
324};
325
326static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kmk[] =
327{
328 &g_ForceNativeEol,
329 &g_StripTrailingBlanks,
330 &g_AdjustTrailingLines,
331 &g_SvnNoExecutable,
332 &g_SvnKeywords,
333 &g_SvnSyncProcess,
334 &g_Copyright_HashComment,
335 &g_Makefile_kmk
336};
337
338static PCSCMREWRITERCFG const g_apRewritersFor_OtherMakefiles[] =
339{
340 &g_ForceNativeEol,
341 &g_StripTrailingBlanks,
342 &g_AdjustTrailingLines,
343 &g_SvnNoExecutable,
344 &g_SvnKeywords,
345 &g_SvnSyncProcess,
346 &g_Copyright_HashComment,
347};
348
349static PCSCMREWRITERCFG const g_apRewritersFor_C_and_CPP[] =
350{
351 &g_ForceNativeEol,
352 &g_ExpandTabs,
353 &g_StripTrailingBlanks,
354 &g_AdjustTrailingLines,
355 &g_SvnNoExecutable,
356 &g_SvnKeywords,
357 &g_SvnSyncProcess,
358 &g_Copyright_CstyleComment,
359 &g_FixFlowerBoxMarkers,
360 &g_Fix_C_and_CPP_Todos,
361 &g_C_and_CPP
362};
363
364static PCSCMREWRITERCFG const g_apRewritersFor_H_and_HPP[] =
365{
366 &g_ForceNativeEol,
367 &g_ExpandTabs,
368 &g_StripTrailingBlanks,
369 &g_AdjustTrailingLines,
370 &g_SvnNoExecutable,
371 &g_SvnKeywords,
372 &g_SvnSyncProcess,
373 &g_Copyright_CstyleComment,
374 &g_C_and_CPP
375};
376
377static PCSCMREWRITERCFG const g_apRewritersFor_RC[] =
378{
379 &g_ForceNativeEol,
380 &g_ExpandTabs,
381 &g_StripTrailingBlanks,
382 &g_AdjustTrailingLines,
383 &g_SvnNoExecutable,
384 &g_SvnKeywords,
385 &g_SvnSyncProcess,
386 &g_Copyright_CstyleComment,
387};
388
389static PCSCMREWRITERCFG const g_apRewritersFor_DTrace[] =
390{
391 &g_ForceNativeEol,
392 &g_ExpandTabs,
393 &g_StripTrailingBlanks,
394 &g_AdjustTrailingLines,
395 &g_SvnKeywords,
396 &g_SvnSyncProcess,
397 &g_Copyright_CstyleComment,
398};
399
400static PCSCMREWRITERCFG const g_apRewritersFor_DSL[] =
401{
402 &g_ForceNativeEol,
403 &g_ExpandTabs,
404 &g_StripTrailingBlanks,
405 &g_AdjustTrailingLines,
406 &g_SvnNoExecutable,
407 &g_SvnKeywords,
408 &g_SvnSyncProcess,
409 &g_Copyright_CstyleComment,
410};
411
412static PCSCMREWRITERCFG const g_apRewritersFor_ASM[] =
413{
414 &g_ForceNativeEol,
415 &g_ExpandTabs,
416 &g_StripTrailingBlanks,
417 &g_AdjustTrailingLines,
418 &g_SvnNoExecutable,
419 &g_SvnKeywords,
420 &g_SvnSyncProcess,
421 &g_Copyright_SemicolonComment,
422};
423
424static PCSCMREWRITERCFG const g_apRewritersFor_DEF[] =
425{
426 &g_ForceNativeEol,
427 &g_ExpandTabs,
428 &g_StripTrailingBlanks,
429 &g_AdjustTrailingLines,
430 &g_SvnNoExecutable,
431 &g_SvnKeywords,
432 &g_SvnSyncProcess,
433 &g_Copyright_SemicolonComment,
434};
435
436static PCSCMREWRITERCFG const g_apRewritersFor_ShellScripts[] =
437{
438 &g_ForceLF,
439 &g_ExpandTabs,
440 &g_StripTrailingBlanks,
441 &g_SvnSyncProcess,
442 &g_Copyright_HashComment,
443};
444
445static PCSCMREWRITERCFG const g_apRewritersFor_BatchFiles[] =
446{
447 &g_ForceCRLF,
448 &g_ExpandTabs,
449 &g_StripTrailingBlanks,
450 &g_SvnSyncProcess,
451 &g_Copyright_RemComment,
452};
453
454static PCSCMREWRITERCFG const g_apRewritersFor_BasicScripts[] =
455{
456 &g_ForceCRLF,
457 &g_ExpandTabs,
458 &g_StripTrailingBlanks,
459 &g_SvnSyncProcess,
460 &g_Copyright_TickComment,
461};
462
463static PCSCMREWRITERCFG const g_apRewritersFor_SedScripts[] =
464{
465 &g_ForceLF,
466 &g_ExpandTabs,
467 &g_StripTrailingBlanks,
468 &g_SvnSyncProcess,
469 &g_Copyright_HashComment,
470};
471
472static PCSCMREWRITERCFG const g_apRewritersFor_Python[] =
473{
474 /** @todo &g_ForceLFIfExecutable */
475 &g_ExpandTabs,
476 &g_StripTrailingBlanks,
477 &g_AdjustTrailingLines,
478 &g_SvnKeywords,
479 &g_SvnSyncProcess,
480 &g_Copyright_PythonComment,
481};
482
483static PCSCMREWRITERCFG const g_apRewritersFor_Perl[] =
484{
485 /** @todo &g_ForceLFIfExecutable */
486 &g_ExpandTabs,
487 &g_StripTrailingBlanks,
488 &g_AdjustTrailingLines,
489 &g_SvnKeywords,
490 &g_SvnSyncProcess,
491 &g_Copyright_HashComment,
492};
493
494static PCSCMREWRITERCFG const g_apRewritersFor_DriverInfFiles[] =
495{
496 &g_ForceNativeEol,
497 &g_ExpandTabs,
498 &g_StripTrailingBlanks,
499 &g_AdjustTrailingLines,
500 &g_SvnKeywords,
501 &g_SvnNoExecutable,
502 &g_SvnSyncProcess,
503 &g_Copyright_SemicolonComment,
504};
505
506static PCSCMREWRITERCFG const g_apRewritersFor_NsisFiles[] =
507{
508 &g_ForceNativeEol,
509 &g_ExpandTabs,
510 &g_StripTrailingBlanks,
511 &g_AdjustTrailingLines,
512 &g_SvnKeywords,
513 &g_SvnNoExecutable,
514 &g_SvnSyncProcess,
515 &g_Copyright_SemicolonComment,
516};
517
518static PCSCMREWRITERCFG const g_apRewritersFor_Java[] =
519{
520 &g_ForceNativeEol,
521 &g_ExpandTabs,
522 &g_StripTrailingBlanks,
523 &g_AdjustTrailingLines,
524 &g_SvnNoExecutable,
525 &g_SvnKeywords,
526 &g_SvnSyncProcess,
527 &g_Copyright_CstyleComment,
528 &g_FixFlowerBoxMarkers,
529 &g_Fix_C_and_CPP_Todos,
530};
531
532static PCSCMREWRITERCFG const g_apRewritersFor_ScmSettings[] =
533{
534 &g_ForceNativeEol,
535 &g_ExpandTabs,
536 &g_StripTrailingBlanks,
537 &g_AdjustTrailingLines,
538 &g_SvnNoExecutable,
539 &g_SvnKeywords,
540 &g_SvnSyncProcess,
541 &g_Copyright_HashComment,
542};
543
544static PCSCMREWRITERCFG const g_apRewritersFor_Images[] =
545{
546 &g_SvnNoExecutable,
547 &g_SvnBinary,
548 &g_SvnSyncProcess,
549};
550
551static PCSCMREWRITERCFG const g_apRewritersFor_Xslt[] =
552{
553 &g_ForceNativeEol,
554 &g_ExpandTabs,
555 &g_StripTrailingBlanks,
556 &g_AdjustTrailingLines,
557 &g_SvnNoExecutable,
558 &g_SvnKeywords,
559 &g_SvnSyncProcess,
560 /** @todo copyright is in an XML comment. */
561};
562
563static PCSCMREWRITERCFG const g_apRewritersFor_Xml[] =
564{
565 &g_ForceNativeEol,
566 &g_ExpandTabs,
567 &g_StripTrailingBlanks,
568 &g_AdjustTrailingLines,
569 &g_SvnNoExecutable,
570 &g_SvnKeywords,
571 &g_SvnSyncProcess,
572 /** @todo copyright is in an XML comment. */
573};
574
575static PCSCMREWRITERCFG const g_apRewritersFor_Wix[] =
576{
577 &g_ForceNativeEol,
578 &g_ExpandTabs,
579 &g_StripTrailingBlanks,
580 &g_AdjustTrailingLines,
581 &g_SvnNoExecutable,
582 &g_SvnKeywords,
583 &g_SvnSyncProcess,
584 /** @todo copyright is in an XML comment. */
585};
586
587static PCSCMREWRITERCFG const g_apRewritersFor_QtProject[] =
588{
589 &g_ForceNativeEol,
590 &g_StripTrailingBlanks,
591 &g_AdjustTrailingLines,
592 &g_SvnNoExecutable,
593 &g_SvnKeywords,
594 &g_SvnSyncProcess,
595 &g_Copyright_HashComment,
596};
597
598static PCSCMREWRITERCFG const g_apRewritersFor_QtResourceFiles[] =
599{
600 &g_ForceNativeEol,
601 &g_SvnNoExecutable,
602 &g_SvnKeywords,
603 &g_SvnSyncProcess,
604 /** @todo figure out copyright for Qt resource XML files. */
605};
606
607static PCSCMREWRITERCFG const g_apRewritersFor_QtTranslations[] =
608{
609 &g_ForceNativeEol,
610 &g_SvnNoExecutable,
611};
612
613static PCSCMREWRITERCFG const g_apRewritersFor_QtUiFiles[] =
614{
615 &g_ForceNativeEol,
616 &g_SvnNoExecutable,
617 &g_SvnKeywords,
618 &g_SvnSyncProcess,
619 /** @todo copyright is in an XML 'comment' element. */
620};
621
622static PCSCMREWRITERCFG const g_apRewritersFor_SifFiles[] =
623{
624 &g_ForceCRLF,
625 &g_ExpandTabs,
626 &g_StripTrailingBlanks,
627 &g_AdjustTrailingLines,
628 &g_SvnKeywords,
629 &g_SvnNoExecutable,
630 &g_SvnSyncProcess,
631 &g_Copyright_SemicolonComment,
632};
633
634static PCSCMREWRITERCFG const g_apRewritersFor_SqlFiles[] =
635{
636 &g_ForceNativeEol,
637 &g_ExpandTabs,
638 &g_StripTrailingBlanks,
639 &g_AdjustTrailingLines,
640 &g_SvnKeywords,
641 &g_SvnNoExecutable,
642 &g_SvnSyncProcess,
643 &g_Copyright_SqlComment,
644};
645
646static PCSCMREWRITERCFG const g_apRewritersFor_GnuAsm[] =
647{
648 &g_ForceNativeEol,
649 &g_ExpandTabs,
650 &g_StripTrailingBlanks,
651 &g_AdjustTrailingLines,
652 &g_SvnKeywords,
653 &g_SvnNoExecutable,
654 &g_SvnSyncProcess,
655 &g_Copyright_CstyleComment,
656};
657
658static PCSCMREWRITERCFG const g_apRewritersFor_TextFiles[] =
659{
660 &g_ForceNativeEol,
661 &g_StripTrailingBlanks,
662 &g_SvnKeywords,
663 &g_SvnNoExecutable,
664 &g_SvnSyncProcess,
665 /** @todo check for plain copyright + license in text files. */
666};
667
668static PCSCMREWRITERCFG const g_apRewritersFor_PlainTextFiles[] =
669{
670 &g_ForceNativeEol,
671 &g_StripTrailingBlanks,
672 &g_SvnKeywords,
673 &g_SvnNoExecutable,
674 &g_SvnSyncProcess,
675};
676
677static PCSCMREWRITERCFG const g_apRewritersFor_BinaryFiles[] =
678{
679 &g_SvnBinary,
680 &g_SvnSyncProcess,
681};
682
683static PCSCMREWRITERCFG const g_apRewritersFor_FileLists[] = /* both makefile and shell script */
684{
685 &g_ForceLF,
686 &g_ExpandTabs,
687 &g_StripTrailingBlanks,
688 &g_AdjustTrailingLines,
689 &g_SvnSyncProcess,
690 &g_Copyright_HashComment,
691};
692
693
694/**
695 * Array of standard rewriter configurations.
696 */
697static SCMCFGENTRY const g_aConfigs[] =
698{
699#define SCM_CFG_ENTRY(a_szName, a_aRewriters, a_fBinary, a_szFilePatterns) \
700 { RT_ELEMENTS(a_aRewriters), &a_aRewriters[0], a_fBinary, a_szFilePatterns, a_szName }
701 SCM_CFG_ENTRY("kup", g_apRewritersFor_Makefile_kup, false, "Makefile.kup" ),
702 SCM_CFG_ENTRY("kmk", g_apRewritersFor_Makefile_kmk, false, "*.kmk" ),
703 SCM_CFG_ENTRY("c", g_apRewritersFor_C_and_CPP, false, "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm" ),
704 SCM_CFG_ENTRY("h", g_apRewritersFor_H_and_HPP, false, "*.h|*.hpp" ),
705 SCM_CFG_ENTRY("rc", g_apRewritersFor_RC, false, "*.rc" ),
706 SCM_CFG_ENTRY("asm", g_apRewritersFor_ASM, false, "*.asm|*.mac|*.inc" ),
707 SCM_CFG_ENTRY("dtrace", g_apRewritersFor_DTrace, false, "*.d" ),
708 SCM_CFG_ENTRY("def", g_apRewritersFor_DEF, false, "*.def" ),
709 SCM_CFG_ENTRY("iasl", g_apRewritersFor_DSL, false, "*.dsl" ),
710 SCM_CFG_ENTRY("shell", g_apRewritersFor_ShellScripts, false, "*.sh|configure" ),
711 SCM_CFG_ENTRY("batch", g_apRewritersFor_BatchFiles, false, "*.bat|*.cmd|*.btm" ),
712 SCM_CFG_ENTRY("vbs", g_apRewritersFor_BasicScripts, false, "*.vbs|*.vb" ),
713 SCM_CFG_ENTRY("sed", g_apRewritersFor_SedScripts, false, "*.sed" ),
714 SCM_CFG_ENTRY("python", g_apRewritersFor_Python, false, "*.py" ),
715 SCM_CFG_ENTRY("perl", g_apRewritersFor_Perl, false, "*.pl|*.pm" ),
716 SCM_CFG_ENTRY("drvinf", g_apRewritersFor_DriverInfFiles, false, "*.inf" ),
717 SCM_CFG_ENTRY("nsis", g_apRewritersFor_NsisFiles, false, "*.nsh|*.nsi|*.nsis" ),
718 SCM_CFG_ENTRY("java", g_apRewritersFor_Java, false, "*.java" ),
719 SCM_CFG_ENTRY("scm", g_apRewritersFor_ScmSettings, false, "*.scm-settings" ),
720 SCM_CFG_ENTRY("image", g_apRewritersFor_Images, true, "*.png|*.bmp|*.jpg|*.pnm|*.ico|*.icns|*.tiff|*.tif|*.xcf|*.gif" ),
721 SCM_CFG_ENTRY("xslt", g_apRewritersFor_Xslt, false, "*.xsl" ),
722 SCM_CFG_ENTRY("xml", g_apRewritersFor_Xml, false, "*.xml" ),
723 SCM_CFG_ENTRY("wix", g_apRewritersFor_Wix, false, "*.wxi|*.wxs|*.wxl" ),
724 SCM_CFG_ENTRY("qt-pro", g_apRewritersFor_QtProject, false, "*.pro" ),
725 SCM_CFG_ENTRY("qt-rc", g_apRewritersFor_QtResourceFiles, false, "*.qrc" ),
726 SCM_CFG_ENTRY("qt-ts", g_apRewritersFor_QtTranslations, false, "*.ts" ),
727 SCM_CFG_ENTRY("qt-ui", g_apRewritersFor_QtUiFiles, false, "*.ui" ),
728 SCM_CFG_ENTRY("sif", g_apRewritersFor_SifFiles, false, "*.sif" ),
729 SCM_CFG_ENTRY("sql", g_apRewritersFor_SqlFiles, false, "*.pgsql|*.sql" ),
730 SCM_CFG_ENTRY("gas", g_apRewritersFor_GnuAsm, false, "*.S" ),
731 SCM_CFG_ENTRY("binary", g_apRewritersFor_BinaryFiles, true, "*.bin|*.pdf|*.zip|*.bz2|*.gz" ),
732 /* These should be be last: */
733 SCM_CFG_ENTRY("make", g_apRewritersFor_OtherMakefiles, false, "Makefile|makefile|GNUmakefile|SMakefile|Makefile.am|Makefile.in|*.cmake" ),
734 SCM_CFG_ENTRY("text", g_apRewritersFor_TextFiles, false, "*.txt|README*|readme*|ReadMe*|NOTE*|TODO*" ),
735 SCM_CFG_ENTRY("plaintext", g_apRewritersFor_PlainTextFiles, false, "LICENSE|ChangeLog|FAQ|AUTHORS|INSTALL|NEWS" ),
736 SCM_CFG_ENTRY("file-list", g_apRewritersFor_FileLists, false, "files_*" ),
737};
738
739
740
741/* -=-=-=-=-=- settings -=-=-=-=-=- */
742
743/**
744 * Delete the given config entry.
745 *
746 * @param pEntry The configuration entry to delete.
747 */
748static void scmCfgEntryDelete(PSCMCFGENTRY pEntry)
749{
750 RTMemFree((void *)pEntry->paRewriters);
751 pEntry->paRewriters = NULL;
752 RTMemFree(pEntry);
753}
754
755/**
756 * Create a new configuration entry.
757 *
758 * @returns The new entry. NULL if out of memory.
759 * @param pEntry The configuration entry to duplicate.
760 */
761static PSCMCFGENTRY scmCfgEntryNew(void)
762{
763 PSCMCFGENTRY pNew = (PSCMCFGENTRY)RTMemAlloc(sizeof(*pNew));
764 if (pNew)
765 {
766 pNew->pszName = "custom";
767 pNew->pszFilePattern = "custom";
768 pNew->cRewriters = 0;
769 pNew->paRewriters = NULL;
770 pNew->fBinary = false;
771 }
772 return pNew;
773}
774
775/**
776 * Duplicate the given config entry.
777 *
778 * @returns The duplicate. NULL if out of memory.
779 * @param pEntry The configuration entry to duplicate.
780 */
781static PSCMCFGENTRY scmCfgEntryDup(PCSCMCFGENTRY pEntry)
782{
783 if (pEntry)
784 {
785 PSCMCFGENTRY pDup = (PSCMCFGENTRY)RTMemDup(pEntry, sizeof(*pEntry));
786 if (pDup)
787 {
788 size_t cbSrcRewriters = sizeof(pEntry->paRewriters[0]) * pEntry->cRewriters;
789 size_t cbDstRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z(pEntry->cRewriters, 8);
790 pDup->paRewriters = (PCSCMREWRITERCFG const *)RTMemDupEx(pEntry->paRewriters, cbSrcRewriters,
791 cbDstRewriters - cbSrcRewriters);
792 if (pDup->paRewriters)
793 return pDup;
794
795 RTMemFree(pDup);
796 }
797 return NULL;
798 }
799 return scmCfgEntryNew();
800}
801
802/**
803 * Adds a rewriter action to the given config entry (--add-action).
804 *
805 * @returns VINF_SUCCESS.
806 * @param pEntry The configuration entry.
807 * @param pAction The rewriter action to add.
808 */
809static int scmCfgEntryAddAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
810{
811 PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
812 if (pEntry->cRewriters % 8 == 0)
813 {
814 size_t cbRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z((pEntry->cRewriters + 1), 8);
815 void *pvNew = RTMemRealloc(paRewriters, cbRewriters);
816 if (pvNew)
817 pEntry->paRewriters = paRewriters = (PCSCMREWRITERCFG *)pvNew;
818 else
819 return VERR_NO_MEMORY;
820 }
821
822 paRewriters[pEntry->cRewriters++] = pAction;
823 return VINF_SUCCESS;
824}
825
826/**
827 * Delets an rewriter action from the given config entry (--del-action).
828 *
829 * @param pEntry The configuration entry.
830 * @param pAction The rewriter action to remove.
831 */
832static void scmCfgEntryDelAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
833{
834 PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
835 size_t const cEntries = pEntry->cRewriters;
836 size_t iDst = 0;
837 for (size_t iSrc = 0; iSrc < cEntries; iSrc++)
838 {
839 PCSCMREWRITERCFG pCurAction = paRewriters[iSrc];
840 if (pCurAction != pAction)
841 paRewriters[iDst++] = pCurAction;
842 }
843 pEntry->cRewriters = iDst;
844}
845
846/**
847 * Init a settings structure with settings from @a pSrc.
848 *
849 * @returns IPRT status code
850 * @param pSettings The settings.
851 * @param pSrc The source settings.
852 */
853static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
854{
855 *pSettings = *pSrc;
856
857 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
858 if (RT_SUCCESS(rc))
859 {
860 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
861 if (RT_SUCCESS(rc))
862 {
863 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
864 if (RT_SUCCESS(rc))
865 {
866 if (!pSrc->fFreeTreatAs)
867 return VINF_SUCCESS;
868
869 pSettings->pTreatAs = scmCfgEntryDup(pSrc->pTreatAs);
870 if (pSettings->pTreatAs)
871 return VINF_SUCCESS;
872
873 RTStrFree(pSettings->pszFilterOutDirs);
874 }
875 RTStrFree(pSettings->pszFilterOutFiles);
876 }
877 RTStrFree(pSettings->pszFilterFiles);
878 }
879
880 pSettings->pszFilterFiles = NULL;
881 pSettings->pszFilterOutFiles = NULL;
882 pSettings->pszFilterOutDirs = NULL;
883 pSettings->pTreatAs = NULL;
884 return rc;
885}
886
887/**
888 * Init a settings structure.
889 *
890 * @returns IPRT status code
891 * @param pSettings The settings.
892 */
893static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
894{
895 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
896}
897
898/**
899 * Deletes the settings, i.e. free any dynamically allocated content.
900 *
901 * @param pSettings The settings.
902 */
903static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
904{
905 if (pSettings)
906 {
907 Assert(pSettings->cchTab != UINT8_MAX);
908 pSettings->cchTab = UINT8_MAX;
909
910 RTStrFree(pSettings->pszFilterFiles);
911 RTStrFree(pSettings->pszFilterOutFiles);
912 RTStrFree(pSettings->pszFilterOutDirs);
913 if (pSettings->fFreeTreatAs)
914 scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
915
916 pSettings->pszFilterOutDirs = NULL;
917 pSettings->pszFilterOutFiles = NULL;
918 pSettings->pszFilterFiles = NULL;
919 pSettings->pTreatAs = NULL;
920 pSettings->fFreeTreatAs = false;
921 }
922}
923
924/**
925 * Processes a RTGetOpt result.
926 *
927 * @retval VINF_SUCCESS if handled.
928 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
929 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
930 *
931 * @param pSettings The settings to change.
932 * @param rc The RTGetOpt return value.
933 * @param pValueUnion The RTGetOpt value union.
934 * @param pchDir The absolute path to the directory relative
935 * components in pchLine should be relative to.
936 * @param cchDir The length of the @a pchDir string.
937 */
938static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion,
939 const char *pchDir, size_t cchDir)
940{
941 Assert(pchDir[cchDir - 1] == '/');
942
943 switch (rc)
944 {
945 case SCMOPT_CONVERT_EOL:
946 pSettings->fConvertEol = true;
947 return VINF_SUCCESS;
948 case SCMOPT_NO_CONVERT_EOL:
949 pSettings->fConvertEol = false;
950 return VINF_SUCCESS;
951
952 case SCMOPT_CONVERT_TABS:
953 pSettings->fConvertTabs = true;
954 return VINF_SUCCESS;
955 case SCMOPT_NO_CONVERT_TABS:
956 pSettings->fConvertTabs = false;
957 return VINF_SUCCESS;
958
959 case SCMOPT_FORCE_FINAL_EOL:
960 pSettings->fForceFinalEol = true;
961 return VINF_SUCCESS;
962 case SCMOPT_NO_FORCE_FINAL_EOL:
963 pSettings->fForceFinalEol = false;
964 return VINF_SUCCESS;
965
966 case SCMOPT_FORCE_TRAILING_LINE:
967 pSettings->fForceTrailingLine = true;
968 return VINF_SUCCESS;
969 case SCMOPT_NO_FORCE_TRAILING_LINE:
970 pSettings->fForceTrailingLine = false;
971 return VINF_SUCCESS;
972
973
974 case SCMOPT_STRIP_TRAILING_BLANKS:
975 pSettings->fStripTrailingBlanks = true;
976 return VINF_SUCCESS;
977 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
978 pSettings->fStripTrailingBlanks = false;
979 return VINF_SUCCESS;
980
981 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS:
982 pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8;
983 return VINF_SUCCESS;
984
985
986 case SCMOPT_STRIP_TRAILING_LINES:
987 pSettings->fStripTrailingLines = true;
988 return VINF_SUCCESS;
989 case SCMOPT_NO_STRIP_TRAILING_LINES:
990 pSettings->fStripTrailingLines = false;
991 return VINF_SUCCESS;
992
993 case SCMOPT_FIX_FLOWER_BOX_MARKERS:
994 pSettings->fFixFlowerBoxMarkers = true;
995 return VINF_SUCCESS;
996 case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS:
997 pSettings->fFixFlowerBoxMarkers = false;
998 return VINF_SUCCESS;
999
1000 case SCMOPT_FIX_TODOS:
1001 pSettings->fFixTodos = true;
1002 return VINF_SUCCESS;
1003 case SCMOPT_NO_FIX_TODOS:
1004 pSettings->fFixTodos = false;
1005 return VINF_SUCCESS;
1006
1007 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
1008 pSettings->fUpdateCopyrightYear = true;
1009 return VINF_SUCCESS;
1010 case SCMOPT_NO_UPDATE_COPYRIGHT_YEAR:
1011 pSettings->fUpdateCopyrightYear = false;
1012 return VINF_SUCCESS;
1013
1014 case SCMOPT_EXTERNAL_COPYRIGHT:
1015 pSettings->fExternalCopyright = true;
1016 return VINF_SUCCESS;
1017 case SCMOPT_NO_EXTERNAL_COPYRIGHT:
1018 pSettings->fExternalCopyright = false;
1019 return VINF_SUCCESS;
1020
1021 case SCMOPT_NO_UPDATE_LICENSE:
1022 pSettings->enmUpdateLicense = kScmLicense_LeaveAlone;
1023 return VINF_SUCCESS;
1024 case SCMOPT_LICENSE_OSE_GPL:
1025 pSettings->enmUpdateLicense = kScmLicense_OseGpl;
1026 return VINF_SUCCESS;
1027 case SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL:
1028 pSettings->enmUpdateLicense = kScmLicense_OseDualGplCddl;
1029 return VINF_SUCCESS;
1030 case SCMOPT_LICENSE_OSE_CDDL:
1031 pSettings->enmUpdateLicense = kScmLicense_OseCddl;
1032 return VINF_SUCCESS;
1033 case SCMOPT_LICENSE_LGPL:
1034 pSettings->enmUpdateLicense = kScmLicense_Lgpl;
1035 return VINF_SUCCESS;
1036 case SCMOPT_LICENSE_MIT:
1037 pSettings->enmUpdateLicense = kScmLicense_Mit;
1038 return VINF_SUCCESS;
1039 case SCMOPT_LICENSE_BASED_ON_MIT:
1040 pSettings->enmUpdateLicense = kScmLicense_BasedOnMit;
1041 return VINF_SUCCESS;
1042
1043 case SCMOPT_LGPL_DISCLAIMER:
1044 pSettings->fLgplDisclaimer = true;
1045 return VINF_SUCCESS;
1046 case SCMOPT_NO_LGPL_DISCLAIMER:
1047 pSettings->fLgplDisclaimer = false;
1048 return VINF_SUCCESS;
1049
1050 case SCMOPT_ONLY_SVN_DIRS:
1051 pSettings->fOnlySvnDirs = true;
1052 return VINF_SUCCESS;
1053 case SCMOPT_NOT_ONLY_SVN_DIRS:
1054 pSettings->fOnlySvnDirs = false;
1055 return VINF_SUCCESS;
1056
1057 case SCMOPT_ONLY_SVN_FILES:
1058 pSettings->fOnlySvnFiles = true;
1059 return VINF_SUCCESS;
1060 case SCMOPT_NOT_ONLY_SVN_FILES:
1061 pSettings->fOnlySvnFiles = false;
1062 return VINF_SUCCESS;
1063
1064 case SCMOPT_SET_SVN_EOL:
1065 pSettings->fSetSvnEol = true;
1066 return VINF_SUCCESS;
1067 case SCMOPT_DONT_SET_SVN_EOL:
1068 pSettings->fSetSvnEol = false;
1069 return VINF_SUCCESS;
1070
1071 case SCMOPT_SET_SVN_EXECUTABLE:
1072 pSettings->fSetSvnExecutable = true;
1073 return VINF_SUCCESS;
1074 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
1075 pSettings->fSetSvnExecutable = false;
1076 return VINF_SUCCESS;
1077
1078 case SCMOPT_SET_SVN_KEYWORDS:
1079 pSettings->fSetSvnKeywords = true;
1080 return VINF_SUCCESS;
1081 case SCMOPT_DONT_SET_SVN_KEYWORDS:
1082 pSettings->fSetSvnKeywords = false;
1083 return VINF_SUCCESS;
1084
1085 case SCMOPT_SKIP_SVN_SYNC_PROCESS:
1086 pSettings->fSkipSvnSyncProcess = true;
1087 return VINF_SUCCESS;
1088 case SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS:
1089 pSettings->fSkipSvnSyncProcess = false;
1090 return VINF_SUCCESS;
1091
1092 case SCMOPT_TAB_SIZE:
1093 if ( pValueUnion->u8 < 1
1094 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
1095 {
1096 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
1097 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
1098 return VERR_OUT_OF_RANGE;
1099 }
1100 pSettings->cchTab = pValueUnion->u8;
1101 return VINF_SUCCESS;
1102
1103 case SCMOPT_WIDTH:
1104 if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200)
1105 {
1106 RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8);
1107 return VERR_OUT_OF_RANGE;
1108 }
1109 pSettings->cchWidth = pValueUnion->u8;
1110 return VINF_SUCCESS;
1111
1112 case SCMOPT_FILTER_OUT_DIRS:
1113 case SCMOPT_FILTER_FILES:
1114 case SCMOPT_FILTER_OUT_FILES:
1115 {
1116 char **ppsz = NULL;
1117 switch (rc)
1118 {
1119 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
1120 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
1121 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
1122 }
1123
1124 /*
1125 * An empty string zaps the current list.
1126 */
1127 if (!*pValueUnion->psz)
1128 return RTStrATruncate(ppsz, 0);
1129
1130 /*
1131 * Non-empty strings are appended to the pattern list.
1132 *
1133 * Strip leading and trailing pattern separators before attempting
1134 * to append it. If it's just separators, don't do anything.
1135 */
1136 const char *pszSrc = pValueUnion->psz;
1137 while (*pszSrc == '|')
1138 pszSrc++;
1139 size_t cchSrc = strlen(pszSrc);
1140 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
1141 cchSrc--;
1142 if (!cchSrc)
1143 return VINF_SUCCESS;
1144
1145 /* Append it pattern by pattern, turning settings-relative paths into absolute ones. */
1146 for (;;)
1147 {
1148 const char *pszEnd = (const char *)memchr(pszSrc, '|', cchSrc);
1149 size_t cchPattern = pszEnd ? pszEnd - pszSrc : cchSrc;
1150 int rc2;
1151 if (*pszSrc == '/')
1152 rc2 = RTStrAAppendExN(ppsz, 3,
1153 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
1154 pchDir, cchDir - 1,
1155 pszSrc, cchPattern);
1156 else
1157 rc2 = RTStrAAppendExN(ppsz, 2,
1158 "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
1159 pszSrc, cchPattern);
1160 if (RT_FAILURE(rc2))
1161 return rc2;
1162
1163 /* next */
1164 cchSrc -= cchPattern;
1165 if (!cchSrc)
1166 return VINF_SUCCESS;
1167 cchSrc -= 1;
1168 pszSrc += cchPattern + 1;
1169 }
1170 /* not reached */
1171 }
1172
1173 case SCMOPT_TREAT_AS:
1174 if (pSettings->fFreeTreatAs)
1175 {
1176 scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
1177 pSettings->pTreatAs = NULL;
1178 pSettings->fFreeTreatAs = false;
1179 }
1180
1181 if (*pValueUnion->psz)
1182 {
1183 /* first check the names, then patterns (legacy). */
1184 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1185 if (strcmp(g_aConfigs[iCfg].pszName, pValueUnion->psz) == 0)
1186 {
1187 pSettings->pTreatAs = &g_aConfigs[iCfg];
1188 return VINF_SUCCESS;
1189 }
1190 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1191 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX,
1192 pValueUnion->psz, RTSTR_MAX, NULL))
1193 {
1194 pSettings->pTreatAs = &g_aConfigs[iCfg];
1195 return VINF_SUCCESS;
1196 }
1197 /* Special help for listing the possibilities? */
1198 if (strcmp(pValueUnion->psz, "help") == 0)
1199 {
1200 RTPrintf("Possible --treat-as values:\n");
1201 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1202 RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
1203 }
1204 return VERR_NOT_FOUND;
1205 }
1206
1207 pSettings->pTreatAs = NULL;
1208 return VINF_SUCCESS;
1209
1210 case SCMOPT_ADD_ACTION:
1211 for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
1212 if (strcmp(g_papRewriterActions[iAction]->pszName, pValueUnion->psz) == 0)
1213 {
1214 PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
1215 if (!pSettings->fFreeTreatAs)
1216 {
1217 pEntry = scmCfgEntryDup(pEntry);
1218 if (!pEntry)
1219 return VERR_NO_MEMORY;
1220 pSettings->pTreatAs = pEntry;
1221 pSettings->fFreeTreatAs = true;
1222 }
1223 return scmCfgEntryAddAction(pEntry, g_papRewriterActions[iAction]);
1224 }
1225 RTMsgError("Unknown --add-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
1226 return VERR_NOT_FOUND;
1227
1228 case SCMOPT_DEL_ACTION:
1229 {
1230 uint32_t cActions = 0;
1231 for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
1232 if (RTStrSimplePatternMatch(pValueUnion->psz, g_papRewriterActions[iAction]->pszName))
1233 {
1234 cActions++;
1235 PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
1236 if (!pSettings->fFreeTreatAs)
1237 {
1238 pEntry = scmCfgEntryDup(pEntry);
1239 if (!pEntry)
1240 return VERR_NO_MEMORY;
1241 pSettings->pTreatAs = pEntry;
1242 pSettings->fFreeTreatAs = true;
1243 }
1244 scmCfgEntryDelAction(pEntry, g_papRewriterActions[iAction]);
1245 if (!strchr(pValueUnion->psz, '*'))
1246 return VINF_SUCCESS;
1247 }
1248 if (cActions > 0)
1249 return VINF_SUCCESS;
1250 RTMsgError("Unknown --del-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
1251 return VERR_NOT_FOUND;
1252 }
1253
1254 default:
1255 return VERR_GETOPT_UNKNOWN_OPTION;
1256 }
1257}
1258
1259/**
1260 * Parses an option string.
1261 *
1262 * @returns IPRT status code.
1263 * @param pBase The base settings structure to apply the options
1264 * to.
1265 * @param pszOptions The options to parse.
1266 * @param pchDir The absolute path to the directory relative
1267 * components in pchLine should be relative to.
1268 * @param cchDir The length of the @a pchDir string.
1269 */
1270static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir)
1271{
1272 int cArgs;
1273 char **papszArgs;
1274 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
1275 if (RT_SUCCESS(rc))
1276 {
1277 RTGETOPTUNION ValueUnion;
1278 RTGETOPTSTATE GetOptState;
1279 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
1280 if (RT_SUCCESS(rc))
1281 {
1282 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1283 {
1284 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir);
1285 if (RT_FAILURE(rc))
1286 break;
1287 }
1288 }
1289 RTGetOptArgvFree(papszArgs);
1290 }
1291
1292 return rc;
1293}
1294
1295/**
1296 * Parses an unterminated option string.
1297 *
1298 * @returns IPRT status code.
1299 * @param pBase The base settings structure to apply the options
1300 * to.
1301 * @param pchLine The line.
1302 * @param cchLine The line length.
1303 * @param pchDir The absolute path to the directory relative
1304 * components in pchLine should be relative to.
1305 * @param cchDir The length of the @a pchDir string.
1306 */
1307static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine,
1308 const char *pchDir, size_t cchDir)
1309{
1310 char *pszLine = RTStrDupN(pchLine, cchLine);
1311 if (!pszLine)
1312 return VERR_NO_MEMORY;
1313 int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir);
1314 RTStrFree(pszLine);
1315 return rc;
1316}
1317
1318/**
1319 * Verifies the options string.
1320 *
1321 * @returns IPRT status code.
1322 * @param pszOptions The options to verify .
1323 */
1324static int scmSettingsBaseVerifyString(const char *pszOptions)
1325{
1326 SCMSETTINGSBASE Base;
1327 int rc = scmSettingsBaseInit(&Base);
1328 if (RT_SUCCESS(rc))
1329 {
1330 rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1);
1331 scmSettingsBaseDelete(&Base);
1332 }
1333 return rc;
1334}
1335
1336/**
1337 * Loads settings found in editor and SCM settings directives within the
1338 * document (@a pStream).
1339 *
1340 * @returns IPRT status code.
1341 * @param pBase The settings base to load settings into.
1342 * @param pStream The stream to scan for settings directives.
1343 */
1344static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
1345{
1346 /** @todo Editor and SCM settings directives in documents. */
1347 RT_NOREF2(pBase, pStream);
1348 return VINF_SUCCESS;
1349}
1350
1351/**
1352 * Creates a new settings file struct, cloning @a pSettings.
1353 *
1354 * @returns IPRT status code.
1355 * @param ppSettings Where to return the new struct.
1356 * @param pSettingsBase The settings to inherit from.
1357 */
1358static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
1359{
1360 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
1361 if (!pSettings)
1362 return VERR_NO_MEMORY;
1363 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
1364 if (RT_SUCCESS(rc))
1365 {
1366 pSettings->pDown = NULL;
1367 pSettings->pUp = NULL;
1368 pSettings->paPairs = NULL;
1369 pSettings->cPairs = 0;
1370 *ppSettings = pSettings;
1371 return VINF_SUCCESS;
1372 }
1373 RTMemFree(pSettings);
1374 return rc;
1375}
1376
1377/**
1378 * Destroys a settings structure.
1379 *
1380 * @param pSettings The settings structure to destroy. NULL is OK.
1381 */
1382static void scmSettingsDestroy(PSCMSETTINGS pSettings)
1383{
1384 if (pSettings)
1385 {
1386 scmSettingsBaseDelete(&pSettings->Base);
1387 for (size_t i = 0; i < pSettings->cPairs; i++)
1388 {
1389 RTStrFree(pSettings->paPairs[i].pszPattern);
1390 RTStrFree(pSettings->paPairs[i].pszOptions);
1391 RTStrFree(pSettings->paPairs[i].pszRelativeTo);
1392 pSettings->paPairs[i].pszPattern = NULL;
1393 pSettings->paPairs[i].pszOptions = NULL;
1394 pSettings->paPairs[i].pszRelativeTo = NULL;
1395 }
1396 RTMemFree(pSettings->paPairs);
1397 pSettings->paPairs = NULL;
1398 RTMemFree(pSettings);
1399 }
1400}
1401
1402/**
1403 * Adds a pattern/options pair to the settings structure.
1404 *
1405 * @returns IPRT status code.
1406 * @param pSettings The settings.
1407 * @param pchLine The line containing the unparsed pair.
1408 * @param cchLine The length of the line.
1409 * @param offColon The offset of the colon into the line.
1410 * @param pchDir The absolute path to the directory relative
1411 * components in pchLine should be relative to.
1412 * @param cchDir The length of the @a pchDir string.
1413 */
1414static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon,
1415 const char *pchDir, size_t cchDir)
1416{
1417 Assert(pchLine[offColon] == ':' && offColon < cchLine);
1418 Assert(pchDir[cchDir - 1] == '/');
1419
1420 /*
1421 * Split the string.
1422 */
1423 size_t cchPattern = offColon;
1424 size_t cchOptions = cchLine - cchPattern - 1;
1425
1426 /* strip spaces everywhere */
1427 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
1428 cchPattern--;
1429 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
1430 cchPattern--, pchLine++;
1431
1432 const char *pchOptions = &pchLine[offColon + 1];
1433 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
1434 cchOptions--;
1435 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
1436 cchOptions--, pchOptions++;
1437
1438 /* Quietly ignore empty patterns and empty options. */
1439 if (!cchOptions || !cchPattern)
1440 return VINF_SUCCESS;
1441
1442 /*
1443 * Prepair the pair and verify the option string.
1444 */
1445 uint32_t iPair = pSettings->cPairs;
1446 if ((iPair % 32) == 0)
1447 {
1448 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
1449 if (!pvNew)
1450 return VERR_NO_MEMORY;
1451 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
1452 }
1453
1454 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
1455 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
1456 pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir);
1457 int rc;
1458 if ( pSettings->paPairs[iPair].pszPattern
1459 && pSettings->paPairs[iPair].pszOptions
1460 && pSettings->paPairs[iPair].pszRelativeTo)
1461 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
1462 else
1463 rc = VERR_NO_MEMORY;
1464
1465 /*
1466 * If it checked out fine, expand any relative paths in the pattern.
1467 */
1468 if (RT_SUCCESS(rc))
1469 {
1470 size_t cPattern = 1;
1471 size_t cRelativePaths = 0;
1472 const char *pszSrc = pSettings->paPairs[iPair].pszPattern;
1473 for (;;)
1474 {
1475 if (*pszSrc == '/')
1476 cRelativePaths++;
1477 pszSrc = strchr(pszSrc, '|');
1478 if (!pszSrc)
1479 break;
1480 pszSrc++;
1481 cPattern++;
1482 }
1483 pSettings->paPairs[iPair].fMultiPattern = cPattern > 1;
1484 if (cRelativePaths > 0)
1485 {
1486 char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1);
1487 if (pszNewPattern)
1488 {
1489 char *pszDst = pszNewPattern;
1490 pszSrc = pSettings->paPairs[iPair].pszPattern;
1491 for (;;)
1492 {
1493 if (*pszSrc == '/')
1494 {
1495 memcpy(pszDst, pchDir, cchDir);
1496 pszDst += cchDir;
1497 pszSrc += 1;
1498 }
1499
1500 /* Look for the next relative path. */
1501 const char *pszSrcNext = strchr(pszSrc, '|');
1502 while (pszSrcNext && pszSrcNext[1] != '/')
1503 pszSrcNext = strchr(pszSrcNext, '|');
1504 if (!pszSrcNext)
1505 break;
1506
1507 /* Copy stuff between current and the next path. */
1508 pszSrcNext++;
1509 memcpy(pszDst, pszSrc, pszSrcNext - pszSrc);
1510 pszDst += pszSrcNext - pszSrc;
1511 pszSrc = pszSrcNext;
1512 }
1513
1514 /* Copy the final portion and replace the pattern. */
1515 strcpy(pszDst, pszSrc);
1516
1517 RTStrFree(pSettings->paPairs[iPair].pszPattern);
1518 pSettings->paPairs[iPair].pszPattern = pszNewPattern;
1519 }
1520 else
1521 rc = VERR_NO_MEMORY;
1522 }
1523 }
1524 if (RT_SUCCESS(rc))
1525 /*
1526 * Commit the pair.
1527 */
1528 pSettings->cPairs = iPair + 1;
1529 else
1530 {
1531 RTStrFree(pSettings->paPairs[iPair].pszPattern);
1532 RTStrFree(pSettings->paPairs[iPair].pszOptions);
1533 RTStrFree(pSettings->paPairs[iPair].pszRelativeTo);
1534 }
1535 return rc;
1536}
1537
1538/**
1539 * Loads in the settings from @a pszFilename.
1540 *
1541 * @returns IPRT status code.
1542 * @param pSettings Where to load the settings file.
1543 * @param pszFilename The file to load.
1544 */
1545static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
1546{
1547 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
1548
1549 /* Turn filename into an absolute path and drop the filename. */
1550 char szAbsPath[RTPATH_MAX];
1551 int rc = RTPathAbs(pszFilename, szAbsPath, sizeof(szAbsPath));
1552 if (RT_FAILURE(rc))
1553 {
1554 RTMsgError("%s: RTPathAbs -> %Rrc\n", pszFilename, rc);
1555 return rc;
1556 }
1557 RTPathChangeToUnixSlashes(szAbsPath, true);
1558 size_t cchDir = RTPathFilename(szAbsPath) - &szAbsPath[0];
1559
1560 /* Try open it.*/
1561 SCMSTREAM Stream;
1562 rc = ScmStreamInitForReading(&Stream, pszFilename);
1563 if (RT_SUCCESS(rc))
1564 {
1565 SCMEOL enmEol;
1566 const char *pchLine;
1567 size_t cchLine;
1568 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1569 {
1570 /* Ignore leading spaces. */
1571 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
1572 pchLine++, cchLine--;
1573
1574 /* Ignore empty lines and comment lines. */
1575 if (cchLine < 1 || *pchLine == '#')
1576 continue;
1577
1578 /* Deal with escaped newlines. */
1579 size_t iFirstLine = ~(size_t)0;
1580 char *pszFreeLine = NULL;
1581 if ( pchLine[cchLine - 1] == '\\'
1582 && ( cchLine < 2
1583 || pchLine[cchLine - 2] != '\\') )
1584 {
1585 iFirstLine = ScmStreamTellLine(&Stream);
1586
1587 cchLine--;
1588 while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
1589 cchLine--;
1590
1591 size_t cchTotal = cchLine;
1592 pszFreeLine = RTStrDupN(pchLine, cchLine);
1593 if (pszFreeLine)
1594 {
1595 /* Append following lines. */
1596 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1597 {
1598 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
1599 pchLine++, cchLine--;
1600
1601 bool const fDone = cchLine == 0
1602 || pchLine[cchLine - 1] != '\\'
1603 || (cchLine >= 2 && pchLine[cchLine - 2] == '\\');
1604 if (!fDone)
1605 {
1606 cchLine--;
1607 while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
1608 cchLine--;
1609 }
1610
1611 rc = RTStrRealloc(&pszFreeLine, cchTotal + 1 + cchLine + 1);
1612 if (RT_FAILURE(rc))
1613 break;
1614 pszFreeLine[cchTotal++] = ' ';
1615 memcpy(&pszFreeLine[cchTotal], pchLine, cchLine);
1616 cchTotal += cchLine;
1617 pszFreeLine[cchTotal] = '\0';
1618
1619 if (fDone)
1620 break;
1621 }
1622 }
1623 else
1624 rc = VERR_NO_STR_MEMORY;
1625
1626 if (RT_FAILURE(rc))
1627 {
1628 RTStrFree(pszFreeLine);
1629 rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: Ran out of memory deal with escaped newlines", pszFilename);
1630 break;
1631 }
1632
1633 pchLine = pszFreeLine;
1634 cchLine = cchTotal;
1635 }
1636
1637 /* What kind of line is it? */
1638 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
1639 if (pchColon)
1640 rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, szAbsPath, cchDir);
1641 else
1642 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, szAbsPath, cchDir);
1643 if (pszFreeLine)
1644 RTStrFree(pszFreeLine);
1645 if (RT_FAILURE(rc))
1646 {
1647 RTMsgError("%s:%d: %Rrc\n",
1648 pszFilename, iFirstLine == ~(size_t)0 ? ScmStreamTellLine(&Stream) : iFirstLine, rc);
1649 break;
1650 }
1651 }
1652
1653 if (RT_SUCCESS(rc))
1654 {
1655 rc = ScmStreamGetStatus(&Stream);
1656 if (RT_FAILURE(rc))
1657 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
1658 }
1659 ScmStreamDelete(&Stream);
1660 }
1661 else
1662 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
1663 return rc;
1664}
1665
1666#if 0 /* unused */
1667/**
1668 * Parse the specified settings file creating a new settings struct from it.
1669 *
1670 * @returns IPRT status code
1671 * @param ppSettings Where to return the new settings.
1672 * @param pszFilename The file to parse.
1673 * @param pSettingsBase The base settings we inherit from.
1674 */
1675static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
1676{
1677 PSCMSETTINGS pSettings;
1678 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
1679 if (RT_SUCCESS(rc))
1680 {
1681 rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename);
1682 if (RT_SUCCESS(rc))
1683 {
1684 *ppSettings = pSettings;
1685 return VINF_SUCCESS;
1686 }
1687
1688 scmSettingsDestroy(pSettings);
1689 }
1690 *ppSettings = NULL;
1691 return rc;
1692}
1693#endif
1694
1695
1696/**
1697 * Create an initial settings structure when starting processing a new file or
1698 * directory.
1699 *
1700 * This will look for .scm-settings files from the root and down to the
1701 * specified directory, combining them into the returned settings structure.
1702 *
1703 * @returns IPRT status code.
1704 * @param ppSettings Where to return the pointer to the top stack
1705 * object.
1706 * @param pBaseSettings The base settings we inherit from (globals
1707 * typically).
1708 * @param pszPath The absolute path to the new directory or file.
1709 */
1710static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
1711{
1712 *ppSettings = NULL; /* try shut up gcc. */
1713
1714 /*
1715 * We'll be working with a stack copy of the path.
1716 */
1717 char szFile[RTPATH_MAX];
1718 size_t cchDir = strlen(pszPath);
1719 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
1720 return VERR_FILENAME_TOO_LONG;
1721
1722 /*
1723 * Create the bottom-most settings.
1724 */
1725 PSCMSETTINGS pSettings;
1726 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
1727 if (RT_FAILURE(rc))
1728 return rc;
1729
1730 /*
1731 * Enumerate the path components from the root and down. Load any setting
1732 * files we find.
1733 */
1734 size_t cComponents = RTPathCountComponents(pszPath);
1735 for (size_t i = 1; i <= cComponents; i++)
1736 {
1737 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
1738 if (RT_SUCCESS(rc))
1739 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
1740 if (RT_FAILURE(rc))
1741 break;
1742 RTPathChangeToUnixSlashes(szFile, true);
1743
1744 if (RTFileExists(szFile))
1745 {
1746 rc = scmSettingsLoadFile(pSettings, szFile);
1747 if (RT_FAILURE(rc))
1748 break;
1749 }
1750 }
1751
1752 if (RT_SUCCESS(rc))
1753 *ppSettings = pSettings;
1754 else
1755 scmSettingsDestroy(pSettings);
1756 return rc;
1757}
1758
1759/**
1760 * Pushes a new settings set onto the stack.
1761 *
1762 * @param ppSettingsStack The pointer to the pointer to the top stack
1763 * element. This will be used as input and output.
1764 * @param pSettings The settings to push onto the stack.
1765 */
1766static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
1767{
1768 PSCMSETTINGS pOld = *ppSettingsStack;
1769 pSettings->pDown = pOld;
1770 pSettings->pUp = NULL;
1771 if (pOld)
1772 pOld->pUp = pSettings;
1773 *ppSettingsStack = pSettings;
1774}
1775
1776/**
1777 * Pushes the settings of the specified directory onto the stack.
1778 *
1779 * We will load any .scm-settings in the directory. A stack entry is added even
1780 * if no settings file was found.
1781 *
1782 * @returns IPRT status code.
1783 * @param ppSettingsStack The pointer to the pointer to the top stack
1784 * element. This will be used as input and output.
1785 * @param pszDir The directory to do this for.
1786 */
1787static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
1788{
1789 char szFile[RTPATH_MAX];
1790 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
1791 if (RT_SUCCESS(rc))
1792 {
1793 RTPathChangeToUnixSlashes(szFile, true);
1794
1795 PSCMSETTINGS pSettings;
1796 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
1797 if (RT_SUCCESS(rc))
1798 {
1799 if (RTFileExists(szFile))
1800 rc = scmSettingsLoadFile(pSettings, szFile);
1801 if (RT_SUCCESS(rc))
1802 {
1803 scmSettingsStackPush(ppSettingsStack, pSettings);
1804 return VINF_SUCCESS;
1805 }
1806
1807 scmSettingsDestroy(pSettings);
1808 }
1809 }
1810 return rc;
1811}
1812
1813
1814/**
1815 * Pops a settings set off the stack.
1816 *
1817 * @returns The popped setttings.
1818 * @param ppSettingsStack The pointer to the pointer to the top stack
1819 * element. This will be used as input and output.
1820 */
1821static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
1822{
1823 PSCMSETTINGS pRet = *ppSettingsStack;
1824 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
1825 *ppSettingsStack = pNew;
1826 if (pNew)
1827 pNew->pUp = NULL;
1828 if (pRet)
1829 {
1830 pRet->pUp = NULL;
1831 pRet->pDown = NULL;
1832 }
1833 return pRet;
1834}
1835
1836/**
1837 * Pops and destroys the top entry of the stack.
1838 *
1839 * @param ppSettingsStack The pointer to the pointer to the top stack
1840 * element. This will be used as input and output.
1841 */
1842static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
1843{
1844 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
1845}
1846
1847/**
1848 * Constructs the base settings for the specified file name.
1849 *
1850 * @returns IPRT status code.
1851 * @param pSettingsStack The top element on the settings stack.
1852 * @param pszFilename The file name.
1853 * @param pszBasename The base name (pointer within @a pszFilename).
1854 * @param cchBasename The length of the base name. (For passing to
1855 * RTStrSimplePatternMultiMatch.)
1856 * @param pBase Base settings to initialize.
1857 */
1858static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
1859 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
1860{
1861 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename);
1862
1863 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
1864 if (RT_SUCCESS(rc))
1865 {
1866 /* find the bottom entry in the stack. */
1867 PCSCMSETTINGS pCur = pSettingsStack;
1868 while (pCur->pDown)
1869 pCur = pCur->pDown;
1870
1871 /* Work our way up thru the stack and look for matching pairs. */
1872 while (pCur)
1873 {
1874 size_t const cPairs = pCur->cPairs;
1875 if (cPairs)
1876 {
1877 for (size_t i = 0; i < cPairs; i++)
1878 if ( !pCur->paPairs[i].fMultiPattern
1879 ? RTStrSimplePatternNMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1880 pszBasename, cchBasename)
1881 || RTStrSimplePatternMatch(pCur->paPairs[i].pszPattern, pszFilename)
1882 : RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1883 pszBasename, cchBasename, NULL)
1884 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1885 pszFilename, RTSTR_MAX, NULL))
1886 {
1887 ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n",
1888 pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions);
1889 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions,
1890 pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo));
1891 if (RT_FAILURE(rc))
1892 break;
1893 }
1894 if (RT_FAILURE(rc))
1895 break;
1896 }
1897
1898 /* advance */
1899 pCur = pCur->pUp;
1900 }
1901 }
1902 if (RT_FAILURE(rc))
1903 scmSettingsBaseDelete(pBase);
1904 return rc;
1905}
1906
1907
1908/* -=-=-=-=-=- misc -=-=-=-=-=- */
1909
1910
1911/**
1912 * Prints the per file banner needed and the message level is high enough.
1913 *
1914 * @param pState The rewrite state.
1915 * @param iLevel The required verbosity level.
1916 */
1917void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel)
1918{
1919 if (iLevel <= g_iVerbosity && !pState->fFirst)
1920 {
1921 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1922 pState->fFirst = true;
1923 }
1924}
1925
1926
1927/**
1928 * Prints a verbose message if the level is high enough.
1929 *
1930 * @param pState The rewrite state. Optional.
1931 * @param iLevel The required verbosity level.
1932 * @param pszFormat The message format string. Can be NULL if we
1933 * only want to trigger the per file message.
1934 * @param ... Format arguments.
1935 */
1936void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
1937{
1938 if (iLevel <= g_iVerbosity)
1939 {
1940 if (pState && !pState->fFirst)
1941 {
1942 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1943 pState->fFirst = true;
1944 }
1945 RTPrintf(pState
1946 ? "%s: info: "
1947 : "%s: info: ",
1948 g_szProgName);
1949 va_list va;
1950 va_start(va, pszFormat);
1951 RTPrintfV(pszFormat, va);
1952 va_end(va);
1953 }
1954}
1955
1956
1957/**
1958 * Prints an error message.
1959 *
1960 * @returns false
1961 * @param pState The rewrite state. Optional.
1962 * @param rc The error code.
1963 * @param pszFormat The message format string.
1964 * @param ... Format arguments.
1965 */
1966bool ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...)
1967{
1968 if (RT_SUCCESS(pState->rc))
1969 pState->rc = rc;
1970
1971 if (!pState->fFirst)
1972 {
1973 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1974 pState->fFirst = true;
1975 }
1976 va_list va;
1977 va_start(va, pszFormat);
1978 RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va);
1979 va_end(va);
1980
1981 return false;
1982}
1983
1984
1985/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
1986
1987
1988/**
1989 * Processes a file.
1990 *
1991 * @returns IPRT status code.
1992 * @param pState The rewriter state.
1993 * @param pszFilename The file name.
1994 * @param pszBasename The base name (pointer within @a pszFilename).
1995 * @param cchBasename The length of the base name. (For passing to
1996 * RTStrSimplePatternMultiMatch.)
1997 * @param pBaseSettings The base settings to use. It's OK to modify
1998 * these.
1999 */
2000static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
2001 PSCMSETTINGSBASE pBaseSettings)
2002{
2003 /*
2004 * Do the file level filtering.
2005 */
2006 if ( pBaseSettings->pszFilterFiles
2007 && *pBaseSettings->pszFilterFiles
2008 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
2009 {
2010 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
2011 g_cFilesSkipped++;
2012 return VINF_SUCCESS;
2013 }
2014 if ( pBaseSettings->pszFilterOutFiles
2015 && *pBaseSettings->pszFilterOutFiles
2016 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
2017 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
2018 {
2019 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
2020 g_cFilesSkipped++;
2021 return VINF_SUCCESS;
2022 }
2023 if ( pBaseSettings->fOnlySvnFiles
2024 && !ScmSvnIsInWorkingCopy(pState))
2025 {
2026 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
2027 g_cFilesNotInSvn++;
2028 return VINF_SUCCESS;
2029 }
2030
2031 /*
2032 * Create an input stream from the file and check that it's text.
2033 */
2034 SCMSTREAM Stream1;
2035 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
2036 if (RT_FAILURE(rc))
2037 {
2038 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
2039 return rc;
2040 }
2041 bool const fIsText = ScmStreamIsText(&Stream1);
2042
2043 /*
2044 * Try find a matching rewrite config for this filename.
2045 */
2046 PCSCMCFGENTRY pCfg = pBaseSettings->pTreatAs;
2047 if (!pCfg)
2048 {
2049 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2050 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
2051 {
2052 pCfg = &g_aConfigs[iCfg];
2053 break;
2054 }
2055 if (!pCfg)
2056 {
2057 /* On failure try check for hash-bang stuff before giving up. */
2058 if (fIsText)
2059 {
2060 SCMEOL enmIgn;
2061 size_t cchFirst;
2062 const char *pchFirst = ScmStreamGetLine(&Stream1, &cchFirst, &enmIgn);
2063 if (cchFirst >= 9 && pchFirst && *pchFirst == '#')
2064 {
2065 do
2066 {
2067 pchFirst++;
2068 cchFirst--;
2069 } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
2070 if (*pchFirst == '!')
2071 {
2072 do
2073 {
2074 pchFirst++;
2075 cchFirst--;
2076 } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
2077 const char *pszTreatAs = NULL;
2078 if ( (cchFirst >= 7 && strncmp(pchFirst, "/bin/sh", 7) == 0)
2079 || (cchFirst >= 9 && strncmp(pchFirst, "/bin/bash", 9) == 0)
2080 || (cchFirst >= 4+9 && strncmp(pchFirst, "/usr/bin/bash", 4+9) == 0) )
2081 pszTreatAs = "shell";
2082 else if ( (cchFirst >= 15 && strncmp(pchFirst, "/usr/bin/python", 15) == 0)
2083 || (cchFirst >= 19 && strncmp(pchFirst, "/usr/bin/env python", 19) == 0) )
2084 pszTreatAs = "python";
2085 else if ( (cchFirst >= 13 && strncmp(pchFirst, "/usr/bin/perl", 13) == 0)
2086 || (cchFirst >= 17 && strncmp(pchFirst, "/usr/bin/env perl", 17) == 0) )
2087 pszTreatAs = "perl";
2088 if (pszTreatAs)
2089 {
2090 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2091 if (strcmp(pszTreatAs, g_aConfigs[iCfg].pszName) == 0)
2092 {
2093 pCfg = &g_aConfigs[iCfg];
2094 break;
2095 }
2096 Assert(pCfg);
2097 }
2098 }
2099 }
2100 ScmStreamRewindForReading(&Stream1);
2101 }
2102 if (!pCfg)
2103 {
2104 ScmVerbose(NULL, 2, "skipping '%s': no rewriters configured\n", pszFilename);
2105 g_cFilesNoRewriters++;
2106 ScmStreamDelete(&Stream1);
2107 return VINF_SUCCESS;
2108 }
2109 }
2110 ScmVerbose(pState, 4, "matched \"%s\" (%s)\n", pCfg->pszFilePattern, pCfg->pszName);
2111 }
2112 else
2113 ScmVerbose(pState, 4, "treat-as \"%s\"\n", pCfg->pszName);
2114
2115 if (fIsText || pCfg->fBinary)
2116 {
2117 ScmVerboseBanner(pState, 3);
2118
2119 /*
2120 * Gather SCM and editor settings from the stream.
2121 */
2122 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
2123 if (RT_SUCCESS(rc))
2124 {
2125 ScmStreamRewindForReading(&Stream1);
2126
2127 /*
2128 * Create two more streams for output and push the text thru all the
2129 * rewriters, switching the two streams around when something is
2130 * actually rewritten. Stream1 remains unchanged.
2131 */
2132 SCMSTREAM Stream2;
2133 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
2134 if (RT_SUCCESS(rc))
2135 {
2136 SCMSTREAM Stream3;
2137 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
2138 if (RT_SUCCESS(rc))
2139 {
2140 bool fModified = false;
2141 PSCMSTREAM pIn = &Stream1;
2142 PSCMSTREAM pOut = &Stream2;
2143 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
2144 {
2145 pState->rc = VINF_SUCCESS;
2146 bool fRc = pCfg->paRewriters[iRw]->pfnRewriter(pState, pIn, pOut, pBaseSettings);
2147 if (RT_FAILURE(pState->rc))
2148 break;
2149 if (fRc)
2150 {
2151 PSCMSTREAM pTmp = pOut;
2152 pOut = pIn == &Stream1 ? &Stream3 : pIn;
2153 pIn = pTmp;
2154 fModified = true;
2155 }
2156
2157 ScmStreamRewindForReading(pIn);
2158 ScmStreamRewindForWriting(pOut);
2159 }
2160
2161 rc = pState->rc;
2162 if (RT_SUCCESS(rc))
2163 {
2164 rc = ScmStreamGetStatus(&Stream1);
2165 if (RT_SUCCESS(rc))
2166 rc = ScmStreamGetStatus(&Stream2);
2167 if (RT_SUCCESS(rc))
2168 rc = ScmStreamGetStatus(&Stream3);
2169 if (RT_SUCCESS(rc))
2170 {
2171 /*
2172 * If rewritten, write it back to disk.
2173 */
2174 if (fModified && !pCfg->fBinary)
2175 {
2176 if (!g_fDryRun)
2177 {
2178 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2179 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
2180 if (RT_FAILURE(rc))
2181 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
2182 }
2183 else
2184 {
2185 ScmVerboseBanner(pState, 1);
2186 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol,
2187 g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars,
2188 pBaseSettings->cchTab, g_pStdOut);
2189 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n",
2190 pszFilename, g_pszChangedSuff);
2191 }
2192 g_cFilesModified++;
2193 }
2194 else if (fModified)
2195 rc = RTMsgErrorRc(VERR_INTERNAL_ERROR, "Rewriters modified binary file! Impossible!");
2196
2197 /*
2198 * If pending SVN property changes, apply them.
2199 */
2200 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
2201 {
2202 if (!g_fDryRun)
2203 {
2204 rc = ScmSvnApplyChanges(pState);
2205 if (RT_FAILURE(rc))
2206 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
2207 }
2208 else
2209 ScmSvnDisplayChanges(pState);
2210 if (!fModified)
2211 g_cFilesModified++;
2212 }
2213
2214 if (!fModified && !pState->cSvnPropChanges)
2215 ScmVerbose(pState, 3, "%s: no change\n", pszFilename);
2216 }
2217 else
2218 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
2219 }
2220 ScmStreamDelete(&Stream3);
2221 }
2222 else
2223 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2224 ScmStreamDelete(&Stream2);
2225 }
2226 else
2227 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2228 }
2229 else
2230 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
2231 }
2232 else
2233 {
2234 ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename);
2235 g_cFilesBinaries++;
2236 }
2237 ScmStreamDelete(&Stream1);
2238
2239 return rc;
2240}
2241
2242/**
2243 * Processes a file.
2244 *
2245 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
2246 * directory recursion method.
2247 *
2248 * @returns IPRT status code.
2249 * @param pszFilename The file name.
2250 * @param pszBasename The base name (pointer within @a pszFilename).
2251 * @param cchBasename The length of the base name. (For passing to
2252 * RTStrSimplePatternMultiMatch.)
2253 * @param pSettingsStack The settings stack (pointer to the top element).
2254 */
2255static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
2256 PSCMSETTINGS pSettingsStack)
2257{
2258 SCMSETTINGSBASE Base;
2259 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
2260 if (RT_SUCCESS(rc))
2261 {
2262 SCMRWSTATE State;
2263 State.pszFilename = pszFilename;
2264 State.fFirst = false;
2265 State.fIsInSvnWorkingCopy = 0;
2266 State.cSvnPropChanges = 0;
2267 State.paSvnPropChanges = NULL;
2268 State.rc = VINF_SUCCESS;
2269
2270 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
2271
2272 size_t i = State.cSvnPropChanges;
2273 while (i-- > 0)
2274 {
2275 RTStrFree(State.paSvnPropChanges[i].pszName);
2276 RTStrFree(State.paSvnPropChanges[i].pszValue);
2277 }
2278 RTMemFree(State.paSvnPropChanges);
2279
2280 scmSettingsBaseDelete(&Base);
2281
2282 g_cFilesProcessed++;
2283 }
2284 return rc;
2285}
2286
2287/**
2288 * Tries to correct RTDIRENTRY_UNKNOWN.
2289 *
2290 * @returns Corrected type.
2291 * @param pszPath The path to the object in question.
2292 */
2293static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
2294{
2295 RTFSOBJINFO Info;
2296 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
2297 if (RT_FAILURE(rc))
2298 return RTDIRENTRYTYPE_UNKNOWN;
2299 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
2300 return RTDIRENTRYTYPE_DIRECTORY;
2301 if (RTFS_IS_FILE(Info.Attr.fMode))
2302 return RTDIRENTRYTYPE_FILE;
2303 return RTDIRENTRYTYPE_UNKNOWN;
2304}
2305
2306/**
2307 * Recurse into a sub-directory and process all the files and directories.
2308 *
2309 * @returns IPRT status code.
2310 * @param pszBuf Path buffer containing the directory path on
2311 * entry. This ends with a dot. This is passed
2312 * along when recursing in order to save stack space
2313 * and avoid needless copying.
2314 * @param cchDir Length of our path in pszbuf.
2315 * @param pEntry Directory entry buffer. This is also passed
2316 * along when recursing to save stack space.
2317 * @param pSettingsStack The settings stack (pointer to the top element).
2318 * @param iRecursion The recursion depth. This is used to restrict
2319 * the recursions.
2320 */
2321static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
2322 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
2323{
2324 int rc;
2325 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
2326
2327 /*
2328 * Make sure we stop somewhere.
2329 */
2330 if (iRecursion > 128)
2331 {
2332 RTMsgError("recursion too deep: %d\n", iRecursion);
2333 return VINF_SUCCESS; /* ignore */
2334 }
2335
2336 /*
2337 * Check if it's excluded by --only-svn-dir.
2338 */
2339 if (pSettingsStack->Base.fOnlySvnDirs)
2340 {
2341 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
2342 return VINF_SUCCESS;
2343 }
2344 g_cDirsProcessed++;
2345
2346 /*
2347 * Try open and read the directory.
2348 */
2349 RTDIR hDir;
2350 rc = RTDirOpenFiltered(&hDir, pszBuf, RTDIRFILTER_NONE, 0 /*fFlags*/);
2351 if (RT_FAILURE(rc))
2352 {
2353 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
2354 return rc;
2355 }
2356 for (;;)
2357 {
2358 /* Read the next entry. */
2359 rc = RTDirRead(hDir, pEntry, NULL);
2360 if (RT_FAILURE(rc))
2361 {
2362 if (rc == VERR_NO_MORE_FILES)
2363 rc = VINF_SUCCESS;
2364 else
2365 RTMsgError("RTDirRead -> %Rrc\n", rc);
2366 break;
2367 }
2368
2369 /* Skip '.' and '..'. */
2370 if ( pEntry->szName[0] == '.'
2371 && ( pEntry->cbName == 1
2372 || ( pEntry->cbName == 2
2373 && pEntry->szName[1] == '.')))
2374 continue;
2375
2376 /* Enter it into the buffer so we've got a full name to work
2377 with when needed. */
2378 if (pEntry->cbName + cchDir >= RTPATH_MAX)
2379 {
2380 RTMsgError("Skipping too long entry: %s", pEntry->szName);
2381 continue;
2382 }
2383 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
2384
2385 /* Figure the type. */
2386 RTDIRENTRYTYPE enmType = pEntry->enmType;
2387 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
2388 enmType = scmFigureUnknownType(pszBuf);
2389
2390 /* Process the file or directory, skip the rest. */
2391 if (enmType == RTDIRENTRYTYPE_FILE)
2392 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
2393 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
2394 {
2395 /* Append the dot for the benefit of the pattern matching. */
2396 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
2397 {
2398 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
2399 continue;
2400 }
2401 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
2402 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
2403
2404 if ( !pSettingsStack->Base.pszFilterOutDirs
2405 || !*pSettingsStack->Base.pszFilterOutDirs
2406 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2407 pEntry->szName, pEntry->cbName, NULL)
2408 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2409 pszBuf, cchSubDir, NULL)
2410 )
2411 )
2412 {
2413 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
2414 if (RT_SUCCESS(rc))
2415 {
2416 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
2417 scmSettingsStackPopAndDestroy(&pSettingsStack);
2418 }
2419 }
2420 }
2421 if (RT_FAILURE(rc))
2422 break;
2423 }
2424 RTDirClose(hDir);
2425 return rc;
2426
2427}
2428
2429/**
2430 * Process a directory tree.
2431 *
2432 * @returns IPRT status code.
2433 * @param pszDir The directory to start with. This is pointer to
2434 * a RTPATH_MAX sized buffer.
2435 */
2436static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
2437{
2438 /*
2439 * Setup the recursion.
2440 */
2441 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
2442 if (RT_SUCCESS(rc))
2443 {
2444 RTPathChangeToUnixSlashes(pszDir, true);
2445
2446 RTDIRENTRY Entry;
2447 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
2448 }
2449 else
2450 RTMsgError("RTPathAppend: %Rrc\n", rc);
2451 return rc;
2452}
2453
2454
2455/**
2456 * Processes a file or directory specified as an command line argument.
2457 *
2458 * @returns IPRT status code
2459 * @param pszSomething What we found in the command line arguments.
2460 * @param pSettingsStack The settings stack (pointer to the top element).
2461 */
2462static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
2463{
2464 char szBuf[RTPATH_MAX];
2465 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
2466 if (RT_SUCCESS(rc))
2467 {
2468 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
2469
2470 PSCMSETTINGS pSettings;
2471 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
2472 if (RT_SUCCESS(rc))
2473 {
2474 scmSettingsStackPush(&pSettingsStack, pSettings);
2475
2476 if (RTFileExists(szBuf))
2477 {
2478 const char *pszBasename = RTPathFilename(szBuf);
2479 if (pszBasename)
2480 {
2481 size_t cchBasename = strlen(pszBasename);
2482 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
2483 }
2484 else
2485 {
2486 RTMsgError("RTPathFilename: NULL\n");
2487 rc = VERR_IS_A_DIRECTORY;
2488 }
2489 }
2490 else
2491 rc = scmProcessDirTree(szBuf, pSettingsStack);
2492
2493 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
2494 Assert(pPopped == pSettings); RT_NOREF_PV(pPopped);
2495 scmSettingsDestroy(pSettings);
2496 }
2497 else
2498 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
2499 }
2500 else
2501 RTMsgError("RTPathAbs: %Rrc\n", rc);
2502 return rc;
2503}
2504
2505/**
2506 * Print some stats.
2507 */
2508static void scmPrintStats(void)
2509{
2510 ScmVerbose(NULL, 0,
2511 g_fDryRun
2512 ? "%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"
2513 : "%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",
2514 g_cFilesModified,
2515 g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s",
2516 g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s",
2517 g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s",
2518 g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies",
2519 g_cFilesNotInSvn, g_cFilesSkipped);
2520}
2521
2522/**
2523 * Display the rewriter actions.
2524 *
2525 * @returns RTEXITCODE_SUCCESS.
2526 */
2527static int scmHelpActions(void)
2528{
2529 RTPrintf("Available rewriter actions:\n");
2530 for (uint32_t i = 0; i < RT_ELEMENTS(g_papRewriterActions); i++)
2531 RTPrintf(" %s\n", g_papRewriterActions[i]->pszName);
2532 return RTEXITCODE_SUCCESS;
2533}
2534
2535/**
2536 * Display the default configuration.
2537 *
2538 * @returns RTEXITCODE_SUCCESS.
2539 */
2540static int scmHelpConfig(void)
2541{
2542 RTPrintf("Rewriter configuration:\n");
2543 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2544 {
2545 RTPrintf("\n %s%s - %s:\n",
2546 g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].fBinary ? " (binary)" : "", g_aConfigs[iCfg].pszFilePattern);
2547 for (size_t i = 0; i < g_aConfigs[iCfg].cRewriters; i++)
2548 RTPrintf(" %s\n", g_aConfigs[iCfg].paRewriters[i]->pszName);
2549 }
2550 return RTEXITCODE_SUCCESS;
2551}
2552
2553/**
2554 * Display the primary help text.
2555 *
2556 * @returns RTEXITCODE_SUCCESS.
2557 * @param paOpts Options.
2558 * @param cOpts Number of options.
2559 */
2560static int scmHelp(PCRTGETOPTDEF paOpts, size_t cOpts)
2561{
2562 RTPrintf("VirtualBox Source Code Massager\n"
2563 "\n"
2564 "Usage: %s [options] <files & dirs>\n"
2565 "\n"
2566 "General options:\n", g_szProgName);
2567 for (size_t i = 0; i < cOpts; i++)
2568 {
2569 /* Grouping. */
2570 switch (paOpts[i].iShort)
2571 {
2572 case SCMOPT_DIFF_IGNORE_EOL:
2573 RTPrintf("\nDiff options (dry runs):\n");
2574 break;
2575 case SCMOPT_CONVERT_EOL:
2576 RTPrintf("\nRewriter action options:\n");
2577 break;
2578 case SCMOPT_ONLY_SVN_DIRS:
2579 RTPrintf("\nInput selection options:\n");
2580 break;
2581 case SCMOPT_TREAT_AS:
2582 RTPrintf("\nMisc options:\n");
2583 break;
2584 }
2585
2586 size_t cExtraAdvance = 0;
2587 if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
2588 {
2589 cExtraAdvance = i + 1 < cOpts
2590 && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
2591 || strstr(paOpts[i+1].pszLong, "-not-") != NULL
2592 || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
2593 || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
2594 || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
2595 );
2596 if (cExtraAdvance)
2597 RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
2598 else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE)
2599 RTPrintf(" %s\n", paOpts[i].pszLong);
2600 else
2601 {
2602 RTPrintf(" %s,\n"
2603 " %s,\n"
2604 " %s,\n"
2605 " %s,\n"
2606 " %s,\n"
2607 " %s,\n"
2608 " %s\n",
2609 paOpts[i].pszLong,
2610 paOpts[i + 1].pszLong,
2611 paOpts[i + 2].pszLong,
2612 paOpts[i + 3].pszLong,
2613 paOpts[i + 4].pszLong,
2614 paOpts[i + 5].pszLong,
2615 paOpts[i + 6].pszLong);
2616 cExtraAdvance = 6;
2617 }
2618 }
2619 else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
2620 switch (paOpts[i].iShort)
2621 {
2622 case SCMOPT_DEL_ACTION:
2623 RTPrintf(" %s pattern\n", paOpts[i].pszLong);
2624 break;
2625 case SCMOPT_FILTER_OUT_DIRS:
2626 case SCMOPT_FILTER_FILES:
2627 case SCMOPT_FILTER_OUT_FILES:
2628 RTPrintf(" %s multi-pattern\n", paOpts[i].pszLong);
2629 break;
2630 default:
2631 RTPrintf(" %s string\n", paOpts[i].pszLong);
2632 }
2633 else
2634 RTPrintf(" %s value\n", paOpts[i].pszLong);
2635 switch (paOpts[i].iShort)
2636 {
2637 case 'd':
2638 case 'D': RTPrintf(" Default: --dry-run\n"); break;
2639 case SCMOPT_CHECK_RUN: RTPrintf(" Default: --dry-run\n"); break;
2640 case 'f': RTPrintf(" Default: none\n"); break;
2641 case 'q':
2642 case 'v': RTPrintf(" Default: -vv\n"); break;
2643 case SCMOPT_HELP_CONFIG: RTPrintf(" Shows the standard file rewriter configurations.\n"); break;
2644 case SCMOPT_HELP_ACTIONS: RTPrintf(" Shows the available rewriter actions.\n"); break;
2645
2646 case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
2647 case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
2648 case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
2649 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
2650 case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
2651
2652 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
2653 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
2654 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
2655 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
2656 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
2657 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
2658 case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
2659 case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
2660
2661 case SCMOPT_FIX_TODOS:
2662 RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos);
2663 break;
2664 case SCMOPT_UPDATE_COPYRIGHT_YEAR:
2665 RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear);
2666 break;
2667 case SCMOPT_EXTERNAL_COPYRIGHT:
2668 RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright);
2669 break;
2670 case SCMOPT_NO_UPDATE_LICENSE:
2671 RTPrintf(" License selection. Default: --license-ose-gpl\n");
2672 break;
2673
2674 case SCMOPT_LGPL_DISCLAIMER:
2675 RTPrintf(" Include LGPL version disclaimer. Default: --no-lgpl-disclaimer\n");
2676 break;
2677
2678 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
2679 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
2680 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
2681 case SCMOPT_SKIP_SVN_SYNC_PROCESS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipSvnSyncProcess); break;
2682 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
2683 case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
2684
2685 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
2686 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
2687 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
2688 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
2689 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
2690
2691 case SCMOPT_TREAT_AS:
2692 RTPrintf(" For treat the input file(s) differently, restting any --add-action.\n"
2693 " If the value is empty defaults will be used again. Possible values:\n");
2694 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2695 RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
2696 break;
2697
2698 case SCMOPT_ADD_ACTION:
2699 RTPrintf(" Adds a rewriter action. The first use after a --treat-as will copy and\n"
2700 " the action list selected by the --treat-as. The actuion list will be\n"
2701 " flushed by --treat-as.\n");
2702 break;
2703
2704 case SCMOPT_DEL_ACTION:
2705 RTPrintf(" Deletes one or more rewriter action (pattern). Best used after\n"
2706 " a --treat-as.\n");
2707 break;
2708
2709 default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
2710 }
2711 i += cExtraAdvance;
2712 }
2713
2714 return RTEXITCODE_SUCCESS;
2715}
2716
2717int main(int argc, char **argv)
2718{
2719 int rc = RTR3InitExe(argc, &argv, 0);
2720 if (RT_FAILURE(rc))
2721 return 1;
2722
2723 /*
2724 * Init the current year.
2725 */
2726 RTTIMESPEC Now;
2727 RTTIME Time;
2728 RTTimeExplode(&Time, RTTimeNow(&Now));
2729 g_uYear = Time.i32Year;
2730
2731 /*
2732 * Init the settings.
2733 */
2734 PSCMSETTINGS pSettings;
2735 rc = scmSettingsCreate(&pSettings, &g_Defaults);
2736 if (RT_FAILURE(rc))
2737 {
2738 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
2739 return 1;
2740 }
2741
2742 /*
2743 * Parse arguments and process input in order (because this is the only
2744 * thing that works at the moment).
2745 */
2746 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
2747 {
2748 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
2749 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
2750 { "--check-run", SCMOPT_CHECK_RUN, RTGETOPT_REQ_NOTHING },
2751 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
2752 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2753 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2754 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2755 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2756 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2757 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2758 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2759 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2760 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2761 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2762 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2763 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2764 };
2765 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
2766
2767 bool fCheckRun = false;
2768 RTGETOPTUNION ValueUnion;
2769 RTGETOPTSTATE GetOptState;
2770 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2771 AssertReleaseRCReturn(rc, 1);
2772
2773 while ( (rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0
2774 && rc != VINF_GETOPT_NOT_OPTION)
2775 {
2776 switch (rc)
2777 {
2778 case 'd':
2779 g_fDryRun = true;
2780 fCheckRun = false;
2781 break;
2782 case 'D':
2783 g_fDryRun = fCheckRun = false;
2784 break;
2785 case SCMOPT_CHECK_RUN:
2786 g_fDryRun = fCheckRun = true;
2787 break;
2788
2789 case 'f':
2790 g_pszFileFilter = ValueUnion.psz;
2791 break;
2792
2793 case 'h':
2794 return scmHelp(s_aOpts, RT_ELEMENTS(s_aOpts));
2795
2796 case SCMOPT_HELP_CONFIG:
2797 return scmHelpConfig();
2798
2799 case SCMOPT_HELP_ACTIONS:
2800 return scmHelpActions();
2801
2802 case 'q':
2803 g_iVerbosity = 0;
2804 break;
2805
2806 case 'v':
2807 g_iVerbosity++;
2808 break;
2809
2810 case 'V':
2811 {
2812 /* The following is assuming that svn does it's job here. */
2813 static const char s_szRev[] = "$Revision: 72568 $";
2814 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
2815 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
2816 return 0;
2817 }
2818
2819 case SCMOPT_DIFF_IGNORE_EOL:
2820 g_fDiffIgnoreEol = true;
2821 break;
2822 case SCMOPT_DIFF_NO_IGNORE_EOL:
2823 g_fDiffIgnoreEol = false;
2824 break;
2825
2826 case SCMOPT_DIFF_IGNORE_SPACE:
2827 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
2828 break;
2829 case SCMOPT_DIFF_NO_IGNORE_SPACE:
2830 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
2831 break;
2832
2833 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
2834 g_fDiffIgnoreLeadingWS = true;
2835 break;
2836 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
2837 g_fDiffIgnoreLeadingWS = false;
2838 break;
2839
2840 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
2841 g_fDiffIgnoreTrailingWS = true;
2842 break;
2843 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
2844 g_fDiffIgnoreTrailingWS = false;
2845 break;
2846
2847 case SCMOPT_DIFF_SPECIAL_CHARS:
2848 g_fDiffSpecialChars = true;
2849 break;
2850 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
2851 g_fDiffSpecialChars = false;
2852 break;
2853
2854 default:
2855 {
2856 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1);
2857 if (RT_SUCCESS(rc2))
2858 break;
2859 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
2860 return 2;
2861 return RTGetOptPrintError(rc, &ValueUnion);
2862 }
2863 }
2864 }
2865
2866 /*
2867 * Process non-options.
2868 */
2869 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
2870 if (rc == VINF_GETOPT_NOT_OPTION)
2871 {
2872 ScmSvnInit();
2873
2874 bool fWarned = g_fDryRun;
2875 while (rc == VINF_GETOPT_NOT_OPTION)
2876 {
2877 if (!fWarned)
2878 {
2879 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
2880 "%s: there is a slight risk that bugs or a full disk may cause\n"
2881 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
2882 "%s: all your changes already. If you didn't, then don't blame\n"
2883 "%s: anyone for not warning you!\n"
2884 "%s:\n"
2885 "%s: Press any key to continue...\n",
2886 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
2887 g_szProgName, g_szProgName);
2888 RTStrmGetCh(g_pStdIn);
2889 fWarned = true;
2890 }
2891
2892 rc = scmProcessSomething(ValueUnion.psz, pSettings);
2893 if (RT_FAILURE(rc))
2894 {
2895 rcExit = RTEXITCODE_FAILURE;
2896 break;
2897 }
2898
2899 /* next */
2900 rc = RTGetOpt(&GetOptState, &ValueUnion);
2901 if (RT_FAILURE(rc))
2902 rcExit = RTGetOptPrintError(rc, &ValueUnion);
2903 }
2904
2905 scmPrintStats();
2906 ScmSvnTerm();
2907 }
2908 else
2909 RTMsgWarning("No files or directories specified. Doing nothing");
2910
2911 scmSettingsDestroy(pSettings);
2912
2913 /* If we're in checking mode, fail if any files needed modification. */
2914 if ( rcExit == RTEXITCODE_SUCCESS
2915 && fCheckRun
2916 && g_cFilesModified > 0)
2917 {
2918 RTMsgError("Checking mode failed! %u file%s needs modifications", g_cFilesBinaries, g_cFilesBinaries > 1 ? "s" : "");
2919 rcExit = RTEXITCODE_FAILURE;
2920 }
2921
2922 return rcExit;
2923}
2924
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