VirtualBox

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

Last change on this file since 44978 was 41966, checked in by vboxsync, 12 years ago

scm: For subversion v1.7, call the svn util to extract the info we need. This is slow, but it works.

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