VirtualBox

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

Last change on this file since 50481 was 48958, checked in by vboxsync, 11 years ago

scm.cpp: Python config.

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