VirtualBox

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

Last change on this file since 57000 was 57000, checked in by vboxsync, 9 years ago

scm: Error formatting fixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.7 KB
Line 
1/* $Id: scm.cpp 57000 2015-07-18 23:42:53Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2015 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* Header Files *
20*******************************************************************************/
21#include <iprt/assert.h>
22#include <iprt/ctype.h>
23#include <iprt/dir.h>
24#include <iprt/env.h>
25#include <iprt/file.h>
26#include <iprt/err.h>
27#include <iprt/getopt.h>
28#include <iprt/initterm.h>
29#include <iprt/mem.h>
30#include <iprt/message.h>
31#include <iprt/param.h>
32#include <iprt/path.h>
33#include <iprt/process.h>
34#include <iprt/stream.h>
35#include <iprt/string.h>
36
37#include "scm.h"
38#include "scmdiff.h"
39
40
41/*******************************************************************************
42* Defined Constants And Macros *
43*******************************************************************************/
44/** The name of the settings files. */
45#define SCM_SETTINGS_FILENAME ".scm-settings"
46
47
48/*******************************************************************************
49* Structures and Typedefs *
50*******************************************************************************/
51
52/**
53 * Option identifiers.
54 *
55 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
56 * clear. So, the option setting a flag (boolean) will have an even
57 * number and the one clearing it will have an odd number.
58 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
59 */
60typedef enum SCMOPT
61{
62 SCMOPT_CONVERT_EOL = 10000,
63 SCMOPT_NO_CONVERT_EOL,
64 SCMOPT_CONVERT_TABS,
65 SCMOPT_NO_CONVERT_TABS,
66 SCMOPT_FORCE_FINAL_EOL,
67 SCMOPT_NO_FORCE_FINAL_EOL,
68 SCMOPT_FORCE_TRAILING_LINE,
69 SCMOPT_NO_FORCE_TRAILING_LINE,
70 SCMOPT_STRIP_TRAILING_BLANKS,
71 SCMOPT_NO_STRIP_TRAILING_BLANKS,
72 SCMOPT_STRIP_TRAILING_LINES,
73 SCMOPT_NO_STRIP_TRAILING_LINES,
74 SCMOPT_ONLY_SVN_DIRS,
75 SCMOPT_NOT_ONLY_SVN_DIRS,
76 SCMOPT_ONLY_SVN_FILES,
77 SCMOPT_NOT_ONLY_SVN_FILES,
78 SCMOPT_SET_SVN_EOL,
79 SCMOPT_DONT_SET_SVN_EOL,
80 SCMOPT_SET_SVN_EXECUTABLE,
81 SCMOPT_DONT_SET_SVN_EXECUTABLE,
82 SCMOPT_SET_SVN_KEYWORDS,
83 SCMOPT_DONT_SET_SVN_KEYWORDS,
84 SCMOPT_TAB_SIZE,
85 SCMOPT_FILTER_OUT_DIRS,
86 SCMOPT_FILTER_FILES,
87 SCMOPT_FILTER_OUT_FILES,
88 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
89 //
90 SCMOPT_DIFF_IGNORE_EOL,
91 SCMOPT_DIFF_NO_IGNORE_EOL,
92 SCMOPT_DIFF_IGNORE_SPACE,
93 SCMOPT_DIFF_NO_IGNORE_SPACE,
94 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
95 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
96 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
97 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
98 SCMOPT_DIFF_SPECIAL_CHARS,
99 SCMOPT_DIFF_NO_SPECIAL_CHARS,
100 SCMOPT_END
101} SCMOPT;
102
103
104/*******************************************************************************
105* Global Variables *
106*******************************************************************************/
107const char g_szTabSpaces[16+1] = " ";
108static const char g_szProgName[] = "scm";
109static const char *g_pszChangedSuff = "";
110static bool g_fDryRun = true;
111static bool g_fDiffSpecialChars = true;
112static bool g_fDiffIgnoreEol = false;
113static bool g_fDiffIgnoreLeadingWS = false;
114static bool g_fDiffIgnoreTrailingWS = false;
115static int g_iVerbosity = 2;//99; //0;
116
117/** The global settings. */
118static SCMSETTINGSBASE const g_Defaults =
119{
120 /* .fConvertEol = */ true,
121 /* .fConvertTabs = */ true,
122 /* .fForceFinalEol = */ true,
123 /* .fForceTrailingLine = */ false,
124 /* .fStripTrailingBlanks = */ true,
125 /* .fStripTrailingLines = */ true,
126 /* .fOnlySvnFiles = */ false,
127 /* .fOnlySvnDirs = */ false,
128 /* .fSetSvnEol = */ false,
129 /* .fSetSvnExecutable = */ false,
130 /* .fSetSvnKeywords = */ false,
131 /* .cchTab = */ 8,
132 /* .pszFilterFiles = */ (char *)"",
133 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
134 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
135};
136
137/** Option definitions for the base settings. */
138static RTGETOPTDEF g_aScmOpts[] =
139{
140 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
141 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
142 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
143 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
144 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
145 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
146 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
147 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
148 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
149 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
150 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
151 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
152 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
153 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
154 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
155 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
156 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
157 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
158 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
159 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
160 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
161 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
162 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
163 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
164 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
165 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
166};
167
168/** Consider files matching the following patterns (base names only). */
169static const char *g_pszFileFilter = NULL;
170
171static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
172{
173 rewrite_SvnNoExecutable,
174 rewrite_Makefile_kup
175};
176
177static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
178{
179 rewrite_ForceNativeEol,
180 rewrite_StripTrailingBlanks,
181 rewrite_AdjustTrailingLines,
182 rewrite_SvnNoExecutable,
183 rewrite_SvnKeywords,
184 rewrite_Makefile_kmk
185};
186
187static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
188{
189 rewrite_ForceNativeEol,
190 rewrite_ExpandTabs,
191 rewrite_StripTrailingBlanks,
192 rewrite_AdjustTrailingLines,
193 rewrite_SvnNoExecutable,
194 rewrite_SvnKeywords,
195 rewrite_C_and_CPP
196};
197
198static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
199{
200 rewrite_ForceNativeEol,
201 rewrite_ExpandTabs,
202 rewrite_StripTrailingBlanks,
203 rewrite_AdjustTrailingLines,
204 rewrite_SvnNoExecutable,
205 rewrite_C_and_CPP
206};
207
208static PFNSCMREWRITER const g_aRewritersFor_RC[] =
209{
210 rewrite_ForceNativeEol,
211 rewrite_ExpandTabs,
212 rewrite_StripTrailingBlanks,
213 rewrite_AdjustTrailingLines,
214 rewrite_SvnNoExecutable,
215 rewrite_SvnKeywords
216};
217
218static PFNSCMREWRITER const g_aRewritersFor_DEF[] =
219{
220 rewrite_ForceNativeEol,
221 rewrite_ExpandTabs,
222 rewrite_StripTrailingBlanks,
223 rewrite_AdjustTrailingLines,
224 rewrite_SvnNoExecutable,
225 rewrite_SvnKeywords
226};
227
228static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
229{
230 rewrite_ForceLF,
231 rewrite_ExpandTabs,
232 rewrite_StripTrailingBlanks
233};
234
235static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
236{
237 rewrite_ForceCRLF,
238 rewrite_ExpandTabs,
239 rewrite_StripTrailingBlanks
240};
241
242static PFNSCMREWRITER const g_aRewritersFor_SedScripts[] =
243{
244 rewrite_ForceLF,
245 rewrite_ExpandTabs,
246 rewrite_StripTrailingBlanks
247};
248
249static PFNSCMREWRITER const g_aRewritersFor_Python[] =
250{
251 /** @todo rewrite_ForceLFIfExecutable */
252 rewrite_ExpandTabs,
253 rewrite_StripTrailingBlanks,
254 rewrite_AdjustTrailingLines,
255 rewrite_SvnKeywords
256};
257
258
259static SCMCFGENTRY const g_aConfigs[] =
260{
261 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
262 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
263 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm" },
264 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
265 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
266 { RT_ELEMENTS(g_aRewritersFor_DEF), &g_aRewritersFor_DEF[0], "*.def" },
267 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
268 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
269 { RT_ELEMENTS(g_aRewritersFor_SedScripts), &g_aRewritersFor_SedScripts[0], "*.sed" },
270 { RT_ELEMENTS(g_aRewritersFor_Python), &g_aRewritersFor_Python[0], "*.py" },
271};
272
273
274
275/* -=-=-=-=-=- settings -=-=-=-=-=- */
276
277
278/**
279 * Init a settings structure with settings from @a pSrc.
280 *
281 * @returns IPRT status code
282 * @param pSettings The settings.
283 * @param pSrc The source settings.
284 */
285static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
286{
287 *pSettings = *pSrc;
288
289 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
290 if (RT_SUCCESS(rc))
291 {
292 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
293 if (RT_SUCCESS(rc))
294 {
295 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
296 if (RT_SUCCESS(rc))
297 return VINF_SUCCESS;
298
299 RTStrFree(pSettings->pszFilterOutFiles);
300 }
301 RTStrFree(pSettings->pszFilterFiles);
302 }
303
304 pSettings->pszFilterFiles = NULL;
305 pSettings->pszFilterOutFiles = NULL;
306 pSettings->pszFilterOutDirs = NULL;
307 return rc;
308}
309
310/**
311 * Init a settings structure.
312 *
313 * @returns IPRT status code
314 * @param pSettings The settings.
315 */
316static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
317{
318 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
319}
320
321/**
322 * Deletes the settings, i.e. free any dynamically allocated content.
323 *
324 * @param pSettings The settings.
325 */
326static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
327{
328 if (pSettings)
329 {
330 Assert(pSettings->cchTab != ~(unsigned)0);
331 pSettings->cchTab = ~(unsigned)0;
332
333 RTStrFree(pSettings->pszFilterFiles);
334 pSettings->pszFilterFiles = NULL;
335
336 RTStrFree(pSettings->pszFilterOutFiles);
337 pSettings->pszFilterOutFiles = NULL;
338
339 RTStrFree(pSettings->pszFilterOutDirs);
340 pSettings->pszFilterOutDirs = NULL;
341 }
342}
343
344
345/**
346 * Processes a RTGetOpt result.
347 *
348 * @retval VINF_SUCCESS if handled.
349 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
350 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
351 *
352 * @param pSettings The settings to change.
353 * @param rc The RTGetOpt return value.
354 * @param pValueUnion The RTGetOpt value union.
355 */
356static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
357{
358 switch (rc)
359 {
360 case SCMOPT_CONVERT_EOL:
361 pSettings->fConvertEol = true;
362 return VINF_SUCCESS;
363 case SCMOPT_NO_CONVERT_EOL:
364 pSettings->fConvertEol = false;
365 return VINF_SUCCESS;
366
367 case SCMOPT_CONVERT_TABS:
368 pSettings->fConvertTabs = true;
369 return VINF_SUCCESS;
370 case SCMOPT_NO_CONVERT_TABS:
371 pSettings->fConvertTabs = false;
372 return VINF_SUCCESS;
373
374 case SCMOPT_FORCE_FINAL_EOL:
375 pSettings->fForceFinalEol = true;
376 return VINF_SUCCESS;
377 case SCMOPT_NO_FORCE_FINAL_EOL:
378 pSettings->fForceFinalEol = false;
379 return VINF_SUCCESS;
380
381 case SCMOPT_FORCE_TRAILING_LINE:
382 pSettings->fForceTrailingLine = true;
383 return VINF_SUCCESS;
384 case SCMOPT_NO_FORCE_TRAILING_LINE:
385 pSettings->fForceTrailingLine = false;
386 return VINF_SUCCESS;
387
388 case SCMOPT_STRIP_TRAILING_BLANKS:
389 pSettings->fStripTrailingBlanks = true;
390 return VINF_SUCCESS;
391 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
392 pSettings->fStripTrailingBlanks = false;
393 return VINF_SUCCESS;
394
395 case SCMOPT_STRIP_TRAILING_LINES:
396 pSettings->fStripTrailingLines = true;
397 return VINF_SUCCESS;
398 case SCMOPT_NO_STRIP_TRAILING_LINES:
399 pSettings->fStripTrailingLines = false;
400 return VINF_SUCCESS;
401
402 case SCMOPT_ONLY_SVN_DIRS:
403 pSettings->fOnlySvnDirs = true;
404 return VINF_SUCCESS;
405 case SCMOPT_NOT_ONLY_SVN_DIRS:
406 pSettings->fOnlySvnDirs = false;
407 return VINF_SUCCESS;
408
409 case SCMOPT_ONLY_SVN_FILES:
410 pSettings->fOnlySvnFiles = true;
411 return VINF_SUCCESS;
412 case SCMOPT_NOT_ONLY_SVN_FILES:
413 pSettings->fOnlySvnFiles = false;
414 return VINF_SUCCESS;
415
416 case SCMOPT_SET_SVN_EOL:
417 pSettings->fSetSvnEol = true;
418 return VINF_SUCCESS;
419 case SCMOPT_DONT_SET_SVN_EOL:
420 pSettings->fSetSvnEol = false;
421 return VINF_SUCCESS;
422
423 case SCMOPT_SET_SVN_EXECUTABLE:
424 pSettings->fSetSvnExecutable = true;
425 return VINF_SUCCESS;
426 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
427 pSettings->fSetSvnExecutable = false;
428 return VINF_SUCCESS;
429
430 case SCMOPT_SET_SVN_KEYWORDS:
431 pSettings->fSetSvnKeywords = true;
432 return VINF_SUCCESS;
433 case SCMOPT_DONT_SET_SVN_KEYWORDS:
434 pSettings->fSetSvnKeywords = false;
435 return VINF_SUCCESS;
436
437 case SCMOPT_TAB_SIZE:
438 if ( pValueUnion->u8 < 1
439 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
440 {
441 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
442 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
443 return VERR_OUT_OF_RANGE;
444 }
445 pSettings->cchTab = pValueUnion->u8;
446 return VINF_SUCCESS;
447
448 case SCMOPT_FILTER_OUT_DIRS:
449 case SCMOPT_FILTER_FILES:
450 case SCMOPT_FILTER_OUT_FILES:
451 {
452 char **ppsz = NULL;
453 switch (rc)
454 {
455 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
456 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
457 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
458 }
459
460 /*
461 * An empty string zaps the current list.
462 */
463 if (!*pValueUnion->psz)
464 return RTStrATruncate(ppsz, 0);
465
466 /*
467 * Non-empty strings are appended to the pattern list.
468 *
469 * Strip leading and trailing pattern separators before attempting
470 * to append it. If it's just separators, don't do anything.
471 */
472 const char *pszSrc = pValueUnion->psz;
473 while (*pszSrc == '|')
474 pszSrc++;
475 size_t cchSrc = strlen(pszSrc);
476 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
477 cchSrc--;
478 if (!cchSrc)
479 return VINF_SUCCESS;
480
481 return RTStrAAppendExN(ppsz, 2,
482 "|", *ppsz && **ppsz ? (size_t)1 : (size_t)0,
483 pszSrc, cchSrc);
484 }
485
486 default:
487 return VERR_GETOPT_UNKNOWN_OPTION;
488 }
489}
490
491/**
492 * Parses an option string.
493 *
494 * @returns IPRT status code.
495 * @param pBase The base settings structure to apply the options
496 * to.
497 * @param pszOptions The options to parse.
498 */
499static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
500{
501 int cArgs;
502 char **papszArgs;
503 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
504 if (RT_SUCCESS(rc))
505 {
506 RTGETOPTUNION ValueUnion;
507 RTGETOPTSTATE GetOptState;
508 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
509 if (RT_SUCCESS(rc))
510 {
511 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
512 {
513 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
514 if (RT_FAILURE(rc))
515 break;
516 }
517 }
518 RTGetOptArgvFree(papszArgs);
519 }
520
521 return rc;
522}
523
524/**
525 * Parses an unterminated option string.
526 *
527 * @returns IPRT status code.
528 * @param pBase The base settings structure to apply the options
529 * to.
530 * @param pchLine The line.
531 * @param cchLine The line length.
532 */
533static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
534{
535 char *pszLine = RTStrDupN(pchLine, cchLine);
536 if (!pszLine)
537 return VERR_NO_MEMORY;
538 int rc = scmSettingsBaseParseString(pBase, pszLine);
539 RTStrFree(pszLine);
540 return rc;
541}
542
543/**
544 * Verifies the options string.
545 *
546 * @returns IPRT status code.
547 * @param pszOptions The options to verify .
548 */
549static int scmSettingsBaseVerifyString(const char *pszOptions)
550{
551 SCMSETTINGSBASE Base;
552 int rc = scmSettingsBaseInit(&Base);
553 if (RT_SUCCESS(rc))
554 {
555 rc = scmSettingsBaseParseString(&Base, pszOptions);
556 scmSettingsBaseDelete(&Base);
557 }
558 return rc;
559}
560
561/**
562 * Loads settings found in editor and SCM settings directives within the
563 * document (@a pStream).
564 *
565 * @returns IPRT status code.
566 * @param pBase The settings base to load settings into.
567 * @param pStream The stream to scan for settings directives.
568 */
569static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
570{
571 /** @todo Editor and SCM settings directives in documents. */
572 return VINF_SUCCESS;
573}
574
575/**
576 * Creates a new settings file struct, cloning @a pSettings.
577 *
578 * @returns IPRT status code.
579 * @param ppSettings Where to return the new struct.
580 * @param pSettingsBase The settings to inherit from.
581 */
582static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
583{
584 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
585 if (!pSettings)
586 return VERR_NO_MEMORY;
587 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
588 if (RT_SUCCESS(rc))
589 {
590 pSettings->pDown = NULL;
591 pSettings->pUp = NULL;
592 pSettings->paPairs = NULL;
593 pSettings->cPairs = 0;
594 *ppSettings = pSettings;
595 return VINF_SUCCESS;
596 }
597 RTMemFree(pSettings);
598 return rc;
599}
600
601/**
602 * Destroys a settings structure.
603 *
604 * @param pSettings The settings structure to destroy. NULL is OK.
605 */
606static void scmSettingsDestroy(PSCMSETTINGS pSettings)
607{
608 if (pSettings)
609 {
610 scmSettingsBaseDelete(&pSettings->Base);
611 for (size_t i = 0; i < pSettings->cPairs; i++)
612 {
613 RTStrFree(pSettings->paPairs[i].pszPattern);
614 RTStrFree(pSettings->paPairs[i].pszOptions);
615 pSettings->paPairs[i].pszPattern = NULL;
616 pSettings->paPairs[i].pszOptions = NULL;
617 }
618 RTMemFree(pSettings->paPairs);
619 pSettings->paPairs = NULL;
620 RTMemFree(pSettings);
621 }
622}
623
624/**
625 * Adds a pattern/options pair to the settings structure.
626 *
627 * @returns IPRT status code.
628 * @param pSettings The settings.
629 * @param pchLine The line containing the unparsed pair.
630 * @param cchLine The length of the line.
631 */
632static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
633{
634 /*
635 * Split the string.
636 */
637 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
638 if (!pchOptions)
639 return VERR_INVALID_PARAMETER;
640 size_t cchPattern = pchOptions - pchLine;
641 size_t cchOptions = cchLine - cchPattern - 1;
642 pchOptions++;
643
644 /* strip spaces everywhere */
645 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
646 cchPattern--;
647 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
648 cchPattern--, pchLine++;
649
650 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
651 cchOptions--;
652 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
653 cchOptions--, pchOptions++;
654
655 /* Quietly ignore empty patterns and empty options. */
656 if (!cchOptions || !cchPattern)
657 return VINF_SUCCESS;
658
659 /*
660 * Add the pair and verify the option string.
661 */
662 uint32_t iPair = pSettings->cPairs;
663 if ((iPair % 32) == 0)
664 {
665 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
666 if (!pvNew)
667 return VERR_NO_MEMORY;
668 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
669 }
670
671 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
672 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
673 int rc;
674 if ( pSettings->paPairs[iPair].pszPattern
675 && pSettings->paPairs[iPair].pszOptions)
676 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
677 else
678 rc = VERR_NO_MEMORY;
679 if (RT_SUCCESS(rc))
680 pSettings->cPairs = iPair + 1;
681 else
682 {
683 RTStrFree(pSettings->paPairs[iPair].pszPattern);
684 RTStrFree(pSettings->paPairs[iPair].pszOptions);
685 }
686 return rc;
687}
688
689/**
690 * Loads in the settings from @a pszFilename.
691 *
692 * @returns IPRT status code.
693 * @param pSettings Where to load the settings file.
694 * @param pszFilename The file to load.
695 */
696static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
697{
698 ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
699
700 SCMSTREAM Stream;
701 int rc = ScmStreamInitForReading(&Stream, pszFilename);
702 if (RT_FAILURE(rc))
703 {
704 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
705 return rc;
706 }
707
708 SCMEOL enmEol;
709 const char *pchLine;
710 size_t cchLine;
711 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
712 {
713 /* Ignore leading spaces. */
714 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
715 pchLine++, cchLine--;
716
717 /* Ignore empty lines and comment lines. */
718 if (cchLine < 1 || *pchLine == '#')
719 continue;
720
721 /* What kind of line is it? */
722 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
723 if (pchColon)
724 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
725 else
726 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
727 if (RT_FAILURE(rc))
728 {
729 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
730 break;
731 }
732 }
733
734 if (RT_SUCCESS(rc))
735 {
736 rc = ScmStreamGetStatus(&Stream);
737 if (RT_FAILURE(rc))
738 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
739 }
740
741 ScmStreamDelete(&Stream);
742 return rc;
743}
744
745/**
746 * Parse the specified settings file creating a new settings struct from it.
747 *
748 * @returns IPRT status code
749 * @param ppSettings Where to return the new settings.
750 * @param pszFilename The file to parse.
751 * @param pSettingsBase The base settings we inherit from.
752 */
753static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
754{
755 PSCMSETTINGS pSettings;
756 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
757 if (RT_SUCCESS(rc))
758 {
759 rc = scmSettingsLoadFile(pSettings, pszFilename);
760 if (RT_SUCCESS(rc))
761 {
762 *ppSettings = pSettings;
763 return VINF_SUCCESS;
764 }
765
766 scmSettingsDestroy(pSettings);
767 }
768 *ppSettings = NULL;
769 return rc;
770}
771
772
773/**
774 * Create an initial settings structure when starting processing a new file or
775 * directory.
776 *
777 * This will look for .scm-settings files from the root and down to the
778 * specified directory, combining them into the returned settings structure.
779 *
780 * @returns IPRT status code.
781 * @param ppSettings Where to return the pointer to the top stack
782 * object.
783 * @param pBaseSettings The base settings we inherit from (globals
784 * typically).
785 * @param pszPath The absolute path to the new directory or file.
786 */
787static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
788{
789 *ppSettings = NULL; /* try shut up gcc. */
790
791 /*
792 * We'll be working with a stack copy of the path.
793 */
794 char szFile[RTPATH_MAX];
795 size_t cchDir = strlen(pszPath);
796 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
797 return VERR_FILENAME_TOO_LONG;
798
799 /*
800 * Create the bottom-most settings.
801 */
802 PSCMSETTINGS pSettings;
803 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
804 if (RT_FAILURE(rc))
805 return rc;
806
807 /*
808 * Enumerate the path components from the root and down. Load any setting
809 * files we find.
810 */
811 size_t cComponents = RTPathCountComponents(pszPath);
812 for (size_t i = 1; i <= cComponents; i++)
813 {
814 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
815 if (RT_SUCCESS(rc))
816 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
817 if (RT_FAILURE(rc))
818 break;
819 if (RTFileExists(szFile))
820 {
821 rc = scmSettingsLoadFile(pSettings, szFile);
822 if (RT_FAILURE(rc))
823 break;
824 }
825 }
826
827 if (RT_SUCCESS(rc))
828 *ppSettings = pSettings;
829 else
830 scmSettingsDestroy(pSettings);
831 return rc;
832}
833
834/**
835 * Pushes a new settings set onto the stack.
836 *
837 * @param ppSettingsStack The pointer to the pointer to the top stack
838 * element. This will be used as input and output.
839 * @param pSettings The settings to push onto the stack.
840 */
841static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
842{
843 PSCMSETTINGS pOld = *ppSettingsStack;
844 pSettings->pDown = pOld;
845 pSettings->pUp = NULL;
846 if (pOld)
847 pOld->pUp = pSettings;
848 *ppSettingsStack = pSettings;
849}
850
851/**
852 * Pushes the settings of the specified directory onto the stack.
853 *
854 * We will load any .scm-settings in the directory. A stack entry is added even
855 * if no settings file was found.
856 *
857 * @returns IPRT status code.
858 * @param ppSettingsStack The pointer to the pointer to the top stack
859 * element. This will be used as input and output.
860 * @param pszDir The directory to do this for.
861 */
862static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
863{
864 char szFile[RTPATH_MAX];
865 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
866 if (RT_SUCCESS(rc))
867 {
868 PSCMSETTINGS pSettings;
869 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
870 if (RT_SUCCESS(rc))
871 {
872 if (RTFileExists(szFile))
873 rc = scmSettingsLoadFile(pSettings, szFile);
874 if (RT_SUCCESS(rc))
875 {
876 scmSettingsStackPush(ppSettingsStack, pSettings);
877 return VINF_SUCCESS;
878 }
879
880 scmSettingsDestroy(pSettings);
881 }
882 }
883 return rc;
884}
885
886
887/**
888 * Pops a settings set off the stack.
889 *
890 * @returns The popped setttings.
891 * @param ppSettingsStack The pointer to the pointer to the top stack
892 * element. This will be used as input and output.
893 */
894static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
895{
896 PSCMSETTINGS pRet = *ppSettingsStack;
897 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
898 *ppSettingsStack = pNew;
899 if (pNew)
900 pNew->pUp = NULL;
901 if (pRet)
902 {
903 pRet->pUp = NULL;
904 pRet->pDown = NULL;
905 }
906 return pRet;
907}
908
909/**
910 * Pops and destroys the top entry of the stack.
911 *
912 * @param ppSettingsStack The pointer to the pointer to the top stack
913 * element. This will be used as input and output.
914 */
915static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
916{
917 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
918}
919
920/**
921 * Constructs the base settings for the specified file name.
922 *
923 * @returns IPRT status code.
924 * @param pSettingsStack The top element on the settings stack.
925 * @param pszFilename The file name.
926 * @param pszBasename The base name (pointer within @a pszFilename).
927 * @param cchBasename The length of the base name. (For passing to
928 * RTStrSimplePatternMultiMatch.)
929 * @param pBase Base settings to initialize.
930 */
931static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
932 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
933{
934 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
935 if (RT_SUCCESS(rc))
936 {
937 /* find the bottom entry in the stack. */
938 PCSCMSETTINGS pCur = pSettingsStack;
939 while (pCur->pDown)
940 pCur = pCur->pDown;
941
942 /* Work our way up thru the stack and look for matching pairs. */
943 while (pCur)
944 {
945 size_t const cPairs = pCur->cPairs;
946 if (cPairs)
947 {
948 for (size_t i = 0; i < cPairs; i++)
949 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
950 pszBasename, cchBasename, NULL)
951 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
952 pszFilename, RTSTR_MAX, NULL))
953 {
954 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
955 if (RT_FAILURE(rc))
956 break;
957 }
958 if (RT_FAILURE(rc))
959 break;
960 }
961
962 /* advance */
963 pCur = pCur->pUp;
964 }
965 }
966 if (RT_FAILURE(rc))
967 scmSettingsBaseDelete(pBase);
968 return rc;
969}
970
971
972/* -=-=-=-=-=- misc -=-=-=-=-=- */
973
974
975/**
976 * Prints a verbose message if the level is high enough.
977 *
978 * @param pState The rewrite state. Optional.
979 * @param iLevel The required verbosity level.
980 * @param pszFormat The message format string. Can be NULL if we
981 * only want to trigger the per file message.
982 * @param ... Format arguments.
983 */
984void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
985{
986 if (iLevel <= g_iVerbosity)
987 {
988 if (pState && !pState->fFirst)
989 {
990 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
991 pState->fFirst = true;
992 }
993 if (pszFormat)
994 {
995 RTPrintf(pState
996 ? "%s: info: "
997 : "%s: info: ",
998 g_szProgName);
999 va_list va;
1000 va_start(va, pszFormat);
1001 RTPrintfV(pszFormat, va);
1002 va_end(va);
1003 }
1004 }
1005}
1006
1007
1008/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
1009
1010
1011/**
1012 * Processes a file.
1013 *
1014 * @returns IPRT status code.
1015 * @param pState The rewriter state.
1016 * @param pszFilename The file name.
1017 * @param pszBasename The base name (pointer within @a pszFilename).
1018 * @param cchBasename The length of the base name. (For passing to
1019 * RTStrSimplePatternMultiMatch.)
1020 * @param pBaseSettings The base settings to use. It's OK to modify
1021 * these.
1022 */
1023static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
1024 PSCMSETTINGSBASE pBaseSettings)
1025{
1026 /*
1027 * Do the file level filtering.
1028 */
1029 if ( pBaseSettings->pszFilterFiles
1030 && *pBaseSettings->pszFilterFiles
1031 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
1032 {
1033 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
1034 return VINF_SUCCESS;
1035 }
1036 if ( pBaseSettings->pszFilterOutFiles
1037 && *pBaseSettings->pszFilterOutFiles
1038 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
1039 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
1040 {
1041 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
1042 return VINF_SUCCESS;
1043 }
1044 if ( pBaseSettings->fOnlySvnFiles
1045 && !ScmSvnIsInWorkingCopy(pState))
1046 {
1047 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
1048 return VINF_SUCCESS;
1049 }
1050
1051 /*
1052 * Try find a matching rewrite config for this filename.
1053 */
1054 PCSCMCFGENTRY pCfg = NULL;
1055 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
1056 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
1057 {
1058 pCfg = &g_aConfigs[iCfg];
1059 break;
1060 }
1061 if (!pCfg)
1062 {
1063 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
1064 return VINF_SUCCESS;
1065 }
1066 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
1067
1068 /*
1069 * Create an input stream from the file and check that it's text.
1070 */
1071 SCMSTREAM Stream1;
1072 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
1073 if (RT_FAILURE(rc))
1074 {
1075 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
1076 return rc;
1077 }
1078 if (ScmStreamIsText(&Stream1))
1079 {
1080 ScmVerbose(pState, 3, NULL);
1081
1082 /*
1083 * Gather SCM and editor settings from the stream.
1084 */
1085 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
1086 if (RT_SUCCESS(rc))
1087 {
1088 ScmStreamRewindForReading(&Stream1);
1089
1090 /*
1091 * Create two more streams for output and push the text thru all the
1092 * rewriters, switching the two streams around when something is
1093 * actually rewritten. Stream1 remains unchanged.
1094 */
1095 SCMSTREAM Stream2;
1096 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
1097 if (RT_SUCCESS(rc))
1098 {
1099 SCMSTREAM Stream3;
1100 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
1101 if (RT_SUCCESS(rc))
1102 {
1103 bool fModified = false;
1104 PSCMSTREAM pIn = &Stream1;
1105 PSCMSTREAM pOut = &Stream2;
1106 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
1107 {
1108 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
1109 if (fRc)
1110 {
1111 PSCMSTREAM pTmp = pOut;
1112 pOut = pIn == &Stream1 ? &Stream3 : pIn;
1113 pIn = pTmp;
1114 fModified = true;
1115 }
1116 ScmStreamRewindForReading(pIn);
1117 ScmStreamRewindForWriting(pOut);
1118 }
1119
1120 rc = ScmStreamGetStatus(&Stream1);
1121 if (RT_SUCCESS(rc))
1122 rc = ScmStreamGetStatus(&Stream2);
1123 if (RT_SUCCESS(rc))
1124 rc = ScmStreamGetStatus(&Stream3);
1125 if (RT_SUCCESS(rc))
1126 {
1127 /*
1128 * If rewritten, write it back to disk.
1129 */
1130 if (fModified)
1131 {
1132 if (!g_fDryRun)
1133 {
1134 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1135 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
1136 if (RT_FAILURE(rc))
1137 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
1138 }
1139 else
1140 {
1141 ScmVerbose(pState, 1, NULL);
1142 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
1143 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
1144 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
1145 }
1146 }
1147
1148 /*
1149 * If pending SVN property changes, apply them.
1150 */
1151 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
1152 {
1153 if (!g_fDryRun)
1154 {
1155 rc = ScmSvnApplyChanges(pState);
1156 if (RT_FAILURE(rc))
1157 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
1158 }
1159 else
1160 ScmSvnDisplayChanges(pState);
1161 }
1162
1163 if (!fModified && !pState->cSvnPropChanges)
1164 ScmVerbose(pState, 3, "no change\n", pszFilename);
1165 }
1166 else
1167 RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
1168 ScmStreamDelete(&Stream3);
1169 }
1170 else
1171 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1172 ScmStreamDelete(&Stream2);
1173 }
1174 else
1175 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
1176 }
1177 else
1178 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
1179 }
1180 else
1181 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
1182 ScmStreamDelete(&Stream1);
1183
1184 return rc;
1185}
1186
1187/**
1188 * Processes a file.
1189 *
1190 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
1191 * directory recursion method.
1192 *
1193 * @returns IPRT status code.
1194 * @param pszFilename The file name.
1195 * @param pszBasename The base name (pointer within @a pszFilename).
1196 * @param cchBasename The length of the base name. (For passing to
1197 * RTStrSimplePatternMultiMatch.)
1198 * @param pSettingsStack The settings stack (pointer to the top element).
1199 */
1200static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
1201 PSCMSETTINGS pSettingsStack)
1202{
1203 SCMSETTINGSBASE Base;
1204 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
1205 if (RT_SUCCESS(rc))
1206 {
1207 SCMRWSTATE State;
1208 State.fFirst = false;
1209 State.pszFilename = pszFilename;
1210 State.cSvnPropChanges = 0;
1211 State.paSvnPropChanges = NULL;
1212
1213 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
1214
1215 size_t i = State.cSvnPropChanges;
1216 while (i-- > 0)
1217 {
1218 RTStrFree(State.paSvnPropChanges[i].pszName);
1219 RTStrFree(State.paSvnPropChanges[i].pszValue);
1220 }
1221 RTMemFree(State.paSvnPropChanges);
1222
1223 scmSettingsBaseDelete(&Base);
1224 }
1225 return rc;
1226}
1227
1228
1229/**
1230 * Tries to correct RTDIRENTRY_UNKNOWN.
1231 *
1232 * @returns Corrected type.
1233 * @param pszPath The path to the object in question.
1234 */
1235static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
1236{
1237 RTFSOBJINFO Info;
1238 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
1239 if (RT_FAILURE(rc))
1240 return RTDIRENTRYTYPE_UNKNOWN;
1241 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
1242 return RTDIRENTRYTYPE_DIRECTORY;
1243 if (RTFS_IS_FILE(Info.Attr.fMode))
1244 return RTDIRENTRYTYPE_FILE;
1245 return RTDIRENTRYTYPE_UNKNOWN;
1246}
1247
1248/**
1249 * Recurse into a sub-directory and process all the files and directories.
1250 *
1251 * @returns IPRT status code.
1252 * @param pszBuf Path buffer containing the directory path on
1253 * entry. This ends with a dot. This is passed
1254 * along when recursing in order to save stack space
1255 * and avoid needless copying.
1256 * @param cchDir Length of our path in pszbuf.
1257 * @param pEntry Directory entry buffer. This is also passed
1258 * along when recursing to save stack space.
1259 * @param pSettingsStack The settings stack (pointer to the top element).
1260 * @param iRecursion The recursion depth. This is used to restrict
1261 * the recursions.
1262 */
1263static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
1264 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
1265{
1266 int rc;
1267 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
1268
1269 /*
1270 * Make sure we stop somewhere.
1271 */
1272 if (iRecursion > 128)
1273 {
1274 RTMsgError("recursion too deep: %d\n", iRecursion);
1275 return VINF_SUCCESS; /* ignore */
1276 }
1277
1278 /*
1279 * Check if it's excluded by --only-svn-dir.
1280 */
1281 if (pSettingsStack->Base.fOnlySvnDirs)
1282 {
1283 if (!ScmSvnIsDirInWorkingCopy(pszBuf))
1284 return VINF_SUCCESS;
1285 }
1286
1287 /*
1288 * Try open and read the directory.
1289 */
1290 PRTDIR pDir;
1291 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
1292 if (RT_FAILURE(rc))
1293 {
1294 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
1295 return rc;
1296 }
1297 for (;;)
1298 {
1299 /* Read the next entry. */
1300 rc = RTDirRead(pDir, pEntry, NULL);
1301 if (RT_FAILURE(rc))
1302 {
1303 if (rc == VERR_NO_MORE_FILES)
1304 rc = VINF_SUCCESS;
1305 else
1306 RTMsgError("RTDirRead -> %Rrc\n", rc);
1307 break;
1308 }
1309
1310 /* Skip '.' and '..'. */
1311 if ( pEntry->szName[0] == '.'
1312 && ( pEntry->cbName == 1
1313 || ( pEntry->cbName == 2
1314 && pEntry->szName[1] == '.')))
1315 continue;
1316
1317 /* Enter it into the buffer so we've got a full name to work
1318 with when needed. */
1319 if (pEntry->cbName + cchDir >= RTPATH_MAX)
1320 {
1321 RTMsgError("Skipping too long entry: %s", pEntry->szName);
1322 continue;
1323 }
1324 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
1325
1326 /* Figure the type. */
1327 RTDIRENTRYTYPE enmType = pEntry->enmType;
1328 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
1329 enmType = scmFigureUnknownType(pszBuf);
1330
1331 /* Process the file or directory, skip the rest. */
1332 if (enmType == RTDIRENTRYTYPE_FILE)
1333 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
1334 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
1335 {
1336 /* Append the dot for the benefit of the pattern matching. */
1337 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
1338 {
1339 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
1340 continue;
1341 }
1342 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
1343 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
1344
1345 if ( !pSettingsStack->Base.pszFilterOutDirs
1346 || !*pSettingsStack->Base.pszFilterOutDirs
1347 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1348 pEntry->szName, pEntry->cbName, NULL)
1349 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
1350 pszBuf, cchSubDir, NULL)
1351 )
1352 )
1353 {
1354 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
1355 if (RT_SUCCESS(rc))
1356 {
1357 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
1358 scmSettingsStackPopAndDestroy(&pSettingsStack);
1359 }
1360 }
1361 }
1362 if (RT_FAILURE(rc))
1363 break;
1364 }
1365 RTDirClose(pDir);
1366 return rc;
1367
1368}
1369
1370/**
1371 * Process a directory tree.
1372 *
1373 * @returns IPRT status code.
1374 * @param pszDir The directory to start with. This is pointer to
1375 * a RTPATH_MAX sized buffer.
1376 */
1377static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
1378{
1379 /*
1380 * Setup the recursion.
1381 */
1382 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
1383 if (RT_SUCCESS(rc))
1384 {
1385 RTDIRENTRY Entry;
1386 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
1387 }
1388 else
1389 RTMsgError("RTPathAppend: %Rrc\n", rc);
1390 return rc;
1391}
1392
1393
1394/**
1395 * Processes a file or directory specified as an command line argument.
1396 *
1397 * @returns IPRT status code
1398 * @param pszSomething What we found in the command line arguments.
1399 * @param pSettingsStack The settings stack (pointer to the top element).
1400 */
1401static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
1402{
1403 char szBuf[RTPATH_MAX];
1404 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
1405 if (RT_SUCCESS(rc))
1406 {
1407 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
1408
1409 PSCMSETTINGS pSettings;
1410 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
1411 if (RT_SUCCESS(rc))
1412 {
1413 scmSettingsStackPush(&pSettingsStack, pSettings);
1414
1415 if (RTFileExists(szBuf))
1416 {
1417 const char *pszBasename = RTPathFilename(szBuf);
1418 if (pszBasename)
1419 {
1420 size_t cchBasename = strlen(pszBasename);
1421 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
1422 }
1423 else
1424 {
1425 RTMsgError("RTPathFilename: NULL\n");
1426 rc = VERR_IS_A_DIRECTORY;
1427 }
1428 }
1429 else
1430 rc = scmProcessDirTree(szBuf, pSettingsStack);
1431
1432 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
1433 Assert(pPopped == pSettings);
1434 scmSettingsDestroy(pSettings);
1435 }
1436 else
1437 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
1438 }
1439 else
1440 RTMsgError("RTPathAbs: %Rrc\n", rc);
1441 return rc;
1442}
1443
1444int main(int argc, char **argv)
1445{
1446 int rc = RTR3InitExe(argc, &argv, 0);
1447 if (RT_FAILURE(rc))
1448 return 1;
1449
1450 /*
1451 * Init the settings.
1452 */
1453 PSCMSETTINGS pSettings;
1454 rc = scmSettingsCreate(&pSettings, &g_Defaults);
1455 if (RT_FAILURE(rc))
1456 {
1457 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
1458 return 1;
1459 }
1460
1461 /*
1462 * Parse arguments and process input in order (because this is the only
1463 * thing that works at the moment).
1464 */
1465 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
1466 {
1467 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
1468 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
1469 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
1470 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
1471 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
1472 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1473 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
1474 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1475 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
1476 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1477 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
1478 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1479 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
1480 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1481 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
1482 };
1483 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
1484
1485 RTGETOPTUNION ValueUnion;
1486 RTGETOPTSTATE GetOptState;
1487 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
1488 AssertReleaseRCReturn(rc, 1);
1489 size_t cProcessed = 0;
1490
1491 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
1492 {
1493 switch (rc)
1494 {
1495 case 'd':
1496 g_fDryRun = true;
1497 break;
1498 case 'D':
1499 g_fDryRun = false;
1500 break;
1501
1502 case 'f':
1503 g_pszFileFilter = ValueUnion.psz;
1504 break;
1505
1506 case 'h':
1507 RTPrintf("VirtualBox Source Code Massager\n"
1508 "\n"
1509 "Usage: %s [options] <files & dirs>\n"
1510 "\n"
1511 "Options:\n", g_szProgName);
1512 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
1513 {
1514 bool fAdvanceTwo = false;
1515 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
1516 {
1517 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
1518 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
1519 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
1520 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
1521 );
1522 if (fAdvanceTwo)
1523 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
1524 else
1525 RTPrintf(" %s\n", s_aOpts[i].pszLong);
1526 }
1527 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
1528 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
1529 else
1530 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
1531 switch (s_aOpts[i].iShort)
1532 {
1533 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
1534 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
1535 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
1536 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
1537 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
1538 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
1539 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
1540 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
1541 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
1542 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
1543 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
1544 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
1545 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
1546 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
1547 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
1548 }
1549 i += fAdvanceTwo;
1550 }
1551 return 1;
1552
1553 case 'q':
1554 g_iVerbosity = 0;
1555 break;
1556
1557 case 'v':
1558 g_iVerbosity++;
1559 break;
1560
1561 case 'V':
1562 {
1563 /* The following is assuming that svn does it's job here. */
1564 static const char s_szRev[] = "$Revision: 57000 $";
1565 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
1566 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
1567 return 0;
1568 }
1569
1570 case SCMOPT_DIFF_IGNORE_EOL:
1571 g_fDiffIgnoreEol = true;
1572 break;
1573 case SCMOPT_DIFF_NO_IGNORE_EOL:
1574 g_fDiffIgnoreEol = false;
1575 break;
1576
1577 case SCMOPT_DIFF_IGNORE_SPACE:
1578 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
1579 break;
1580 case SCMOPT_DIFF_NO_IGNORE_SPACE:
1581 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
1582 break;
1583
1584 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
1585 g_fDiffIgnoreLeadingWS = true;
1586 break;
1587 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
1588 g_fDiffIgnoreLeadingWS = false;
1589 break;
1590
1591 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
1592 g_fDiffIgnoreTrailingWS = true;
1593 break;
1594 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
1595 g_fDiffIgnoreTrailingWS = false;
1596 break;
1597
1598 case SCMOPT_DIFF_SPECIAL_CHARS:
1599 g_fDiffSpecialChars = true;
1600 break;
1601 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
1602 g_fDiffSpecialChars = false;
1603 break;
1604
1605 case VINF_GETOPT_NOT_OPTION:
1606 {
1607 if (!g_fDryRun)
1608 {
1609 if (!cProcessed)
1610 {
1611 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
1612 "%s: there is a slight risk that bugs or a full disk may cause\n"
1613 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
1614 "%s: all your changes already. If you didn't, then don't blame\n"
1615 "%s: anyone for not warning you!\n"
1616 "%s:\n"
1617 "%s: Press any key to continue...\n",
1618 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
1619 g_szProgName, g_szProgName);
1620 RTStrmGetCh(g_pStdIn);
1621 }
1622 cProcessed++;
1623 }
1624 rc = scmProcessSomething(ValueUnion.psz, pSettings);
1625 if (RT_FAILURE(rc))
1626 return rc;
1627 break;
1628 }
1629
1630 default:
1631 {
1632 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
1633 if (RT_SUCCESS(rc2))
1634 break;
1635 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
1636 return 2;
1637 return RTGetOptPrintError(rc, &ValueUnion);
1638 }
1639 }
1640 }
1641
1642 scmSettingsDestroy(pSettings);
1643 return 0;
1644}
1645
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