VirtualBox

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

Last change on this file since 40530 was 40530, checked in by vboxsync, 13 years ago

scm: Splitting out the diff code while I'm at it.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 93.4 KB
Line 
1/* $Id: scm.cpp 40530 2012-03-19 11:13:17Z 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 "scmstream.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/** Pointer to const massager settings. */
52typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
53
54/**
55 * SVN property.
56 */
57typedef struct SCMSVNPROP
58{
59 /** The property. */
60 char *pszName;
61 /** The value.
62 * When used to record updates, this can be set to NULL to trigger the
63 * deletion of the property. */
64 char *pszValue;
65} SCMSVNPROP;
66/** Pointer to a SVN property. */
67typedef SCMSVNPROP *PSCMSVNPROP;
68/** Pointer to a const SVN property. */
69typedef SCMSVNPROP const *PCSCMSVNPROP;
70
71
72/**
73 * Rewriter state.
74 */
75typedef struct SCMRWSTATE
76{
77 /** The filename. */
78 const char *pszFilename;
79 /** Set after the printing the first verbose message about a file under
80 * rewrite. */
81 bool fFirst;
82 /** The number of SVN property changes. */
83 size_t cSvnPropChanges;
84 /** Pointer to an array of SVN property changes. */
85 PSCMSVNPROP paSvnPropChanges;
86} SCMRWSTATE;
87/** Pointer to the rewriter state. */
88typedef SCMRWSTATE *PSCMRWSTATE;
89
90/**
91 * A rewriter.
92 *
93 * This works like a stream editor, reading @a pIn, modifying it and writing it
94 * to @a pOut.
95 *
96 * @returns true if any changes were made, false if not.
97 * @param pIn The input stream.
98 * @param pOut The output stream.
99 * @param pSettings The settings.
100 */
101typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
102
103
104/**
105 * Configuration entry.
106 */
107typedef struct SCMCFGENTRY
108{
109 /** Number of rewriters. */
110 size_t cRewriters;
111 /** Pointer to an array of rewriters. */
112 PFNSCMREWRITER const *papfnRewriter;
113 /** File pattern (simple). */
114 const char *pszFilePattern;
115} SCMCFGENTRY;
116typedef SCMCFGENTRY *PSCMCFGENTRY;
117typedef SCMCFGENTRY const *PCSCMCFGENTRY;
118
119
120/**
121 * Source Code Massager Settings.
122 */
123typedef struct SCMSETTINGSBASE
124{
125 bool fConvertEol;
126 bool fConvertTabs;
127 bool fForceFinalEol;
128 bool fForceTrailingLine;
129 bool fStripTrailingBlanks;
130 bool fStripTrailingLines;
131 /** Only process files that are part of a SVN working copy. */
132 bool fOnlySvnFiles;
133 /** Only recurse into directories containing an .svn dir. */
134 bool fOnlySvnDirs;
135 /** Set svn:eol-style if missing or incorrect. */
136 bool fSetSvnEol;
137 /** Set svn:executable according to type (unusually this means deleting it). */
138 bool fSetSvnExecutable;
139 /** Set svn:keyword if completely or partially missing. */
140 bool fSetSvnKeywords;
141 /** */
142 unsigned cchTab;
143 /** Only consider files matching these patterns. This is only applied to the
144 * base names. */
145 char *pszFilterFiles;
146 /** Filter out files matching the following patterns. This is applied to base
147 * names as well as the absolute paths. */
148 char *pszFilterOutFiles;
149 /** Filter out directories matching the following patterns. This is applied
150 * to base names as well as the absolute paths. All absolute paths ends with a
151 * slash and dot ("/."). */
152 char *pszFilterOutDirs;
153} SCMSETTINGSBASE;
154/** Pointer to massager settings. */
155typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
156
157/**
158 * Option identifiers.
159 *
160 * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
161 * clear. So, the option setting a flag (boolean) will have an even
162 * number and the one clearing it will have an odd number.
163 * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
164 */
165typedef enum SCMOPT
166{
167 SCMOPT_CONVERT_EOL = 10000,
168 SCMOPT_NO_CONVERT_EOL,
169 SCMOPT_CONVERT_TABS,
170 SCMOPT_NO_CONVERT_TABS,
171 SCMOPT_FORCE_FINAL_EOL,
172 SCMOPT_NO_FORCE_FINAL_EOL,
173 SCMOPT_FORCE_TRAILING_LINE,
174 SCMOPT_NO_FORCE_TRAILING_LINE,
175 SCMOPT_STRIP_TRAILING_BLANKS,
176 SCMOPT_NO_STRIP_TRAILING_BLANKS,
177 SCMOPT_STRIP_TRAILING_LINES,
178 SCMOPT_NO_STRIP_TRAILING_LINES,
179 SCMOPT_ONLY_SVN_DIRS,
180 SCMOPT_NOT_ONLY_SVN_DIRS,
181 SCMOPT_ONLY_SVN_FILES,
182 SCMOPT_NOT_ONLY_SVN_FILES,
183 SCMOPT_SET_SVN_EOL,
184 SCMOPT_DONT_SET_SVN_EOL,
185 SCMOPT_SET_SVN_EXECUTABLE,
186 SCMOPT_DONT_SET_SVN_EXECUTABLE,
187 SCMOPT_SET_SVN_KEYWORDS,
188 SCMOPT_DONT_SET_SVN_KEYWORDS,
189 SCMOPT_TAB_SIZE,
190 SCMOPT_FILTER_OUT_DIRS,
191 SCMOPT_FILTER_FILES,
192 SCMOPT_FILTER_OUT_FILES,
193 SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
194 //
195 SCMOPT_DIFF_IGNORE_EOL,
196 SCMOPT_DIFF_NO_IGNORE_EOL,
197 SCMOPT_DIFF_IGNORE_SPACE,
198 SCMOPT_DIFF_NO_IGNORE_SPACE,
199 SCMOPT_DIFF_IGNORE_LEADING_SPACE,
200 SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
201 SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
202 SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
203 SCMOPT_DIFF_SPECIAL_CHARS,
204 SCMOPT_DIFF_NO_SPECIAL_CHARS,
205 SCMOPT_END
206} SCMOPT;
207
208
209/**
210 * File/dir pattern + options.
211 */
212typedef struct SCMPATRNOPTPAIR
213{
214 char *pszPattern;
215 char *pszOptions;
216} SCMPATRNOPTPAIR;
217/** Pointer to a pattern + option pair. */
218typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
219
220
221/** Pointer to a settings set. */
222typedef struct SCMSETTINGS *PSCMSETTINGS;
223/**
224 * Settings set.
225 *
226 * This structure is constructed from the command line arguments or any
227 * .scm-settings file found in a directory we recurse into. When recursing in
228 * and out of a directory, we push and pop a settings set for it.
229 *
230 * The .scm-settings file has two kinds of setttings, first there are the
231 * unqualified base settings and then there are the settings which applies to a
232 * set of files or directories. The former are lines with command line options.
233 * For the latter, the options are preceded by a string pattern and a colon.
234 * The pattern specifies which files (and/or directories) the options applies
235 * to.
236 *
237 * We parse the base options into the Base member and put the others into the
238 * paPairs array.
239 */
240typedef struct SCMSETTINGS
241{
242 /** Pointer to the setting file below us in the stack. */
243 PSCMSETTINGS pDown;
244 /** Pointer to the setting file above us in the stack. */
245 PSCMSETTINGS pUp;
246 /** File/dir patterns and their options. */
247 PSCMPATRNOPTPAIR paPairs;
248 /** The number of entires in paPairs. */
249 uint32_t cPairs;
250 /** The base settings that was read out of the file. */
251 SCMSETTINGSBASE Base;
252} SCMSETTINGS;
253/** Pointer to a const settings set. */
254typedef SCMSETTINGS const *PCSCMSETTINGS;
255
256
257/*******************************************************************************
258* Internal Functions *
259*******************************************************************************/
260static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
261static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
262static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
263static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
264static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
265static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
266static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
267static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
268static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
269static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
270static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
271
272
273/*******************************************************************************
274* Global Variables *
275*******************************************************************************/
276static const char g_szProgName[] = "scm";
277static const char *g_pszChangedSuff = "";
278static const char g_szTabSpaces[16+1] = " ";
279static bool g_fDryRun = true;
280static bool g_fDiffSpecialChars = true;
281static bool g_fDiffIgnoreEol = false;
282static bool g_fDiffIgnoreLeadingWS = false;
283static bool g_fDiffIgnoreTrailingWS = false;
284static int g_iVerbosity = 2;//99; //0;
285
286/** The global settings. */
287static SCMSETTINGSBASE const g_Defaults =
288{
289 /* .fConvertEol = */ true,
290 /* .fConvertTabs = */ true,
291 /* .fForceFinalEol = */ true,
292 /* .fForceTrailingLine = */ false,
293 /* .fStripTrailingBlanks = */ true,
294 /* .fStripTrailingLines = */ true,
295 /* .fOnlySvnFiles = */ false,
296 /* .fOnlySvnDirs = */ false,
297 /* .fSetSvnEol = */ false,
298 /* .fSetSvnExecutable = */ false,
299 /* .fSetSvnKeywords = */ false,
300 /* .cchTab = */ 8,
301 /* .pszFilterFiles = */ (char *)"",
302 /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
303 /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
304};
305
306/** Option definitions for the base settings. */
307static RTGETOPTDEF g_aScmOpts[] =
308{
309 { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
310 { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
311 { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
312 { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
313 { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
314 { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
315 { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
316 { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
317 { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
318 { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
319 { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
320 { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
321 { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
322 { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
323 { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
324 { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
325 { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
326 { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
327 { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
328 { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
329 { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
330 { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
331 { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
332 { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
333 { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
334 { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
335};
336
337/** Consider files matching the following patterns (base names only). */
338static const char *g_pszFileFilter = NULL;
339
340static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
341{
342 rewrite_SvnNoExecutable,
343 rewrite_Makefile_kup
344};
345
346static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
347{
348 rewrite_ForceNativeEol,
349 rewrite_StripTrailingBlanks,
350 rewrite_AdjustTrailingLines,
351 rewrite_SvnNoExecutable,
352 rewrite_SvnKeywords,
353 rewrite_Makefile_kmk
354};
355
356static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
357{
358 rewrite_ForceNativeEol,
359 rewrite_ExpandTabs,
360 rewrite_StripTrailingBlanks,
361 rewrite_AdjustTrailingLines,
362 rewrite_SvnNoExecutable,
363 rewrite_SvnKeywords,
364 rewrite_C_and_CPP
365};
366
367static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
368{
369 rewrite_ForceNativeEol,
370 rewrite_ExpandTabs,
371 rewrite_StripTrailingBlanks,
372 rewrite_AdjustTrailingLines,
373 rewrite_SvnNoExecutable,
374 rewrite_C_and_CPP
375};
376
377static PFNSCMREWRITER const g_aRewritersFor_RC[] =
378{
379 rewrite_ForceNativeEol,
380 rewrite_ExpandTabs,
381 rewrite_StripTrailingBlanks,
382 rewrite_AdjustTrailingLines,
383 rewrite_SvnNoExecutable,
384 rewrite_SvnKeywords
385};
386
387static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
388{
389 rewrite_ForceLF,
390 rewrite_ExpandTabs,
391 rewrite_StripTrailingBlanks
392};
393
394static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
395{
396 rewrite_ForceCRLF,
397 rewrite_ExpandTabs,
398 rewrite_StripTrailingBlanks
399};
400
401static SCMCFGENTRY const g_aConfigs[] =
402{
403 { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
404 { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
405 { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
406 { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
407 { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
408 { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
409 { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
410};
411
412
413
414/* -=-=-=-=-=- settings -=-=-=-=-=- */
415
416
417/**
418 * Init a settings structure with settings from @a pSrc.
419 *
420 * @returns IPRT status code
421 * @param pSettings The settings.
422 * @param pSrc The source settings.
423 */
424static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
425{
426 *pSettings = *pSrc;
427
428 int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
429 if (RT_SUCCESS(rc))
430 {
431 rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
432 if (RT_SUCCESS(rc))
433 {
434 rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
435 if (RT_SUCCESS(rc))
436 return VINF_SUCCESS;
437
438 RTStrFree(pSettings->pszFilterOutFiles);
439 }
440 RTStrFree(pSettings->pszFilterFiles);
441 }
442
443 pSettings->pszFilterFiles = NULL;
444 pSettings->pszFilterOutFiles = NULL;
445 pSettings->pszFilterOutDirs = NULL;
446 return rc;
447}
448
449/**
450 * Init a settings structure.
451 *
452 * @returns IPRT status code
453 * @param pSettings The settings.
454 */
455static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
456{
457 return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
458}
459
460/**
461 * Deletes the settings, i.e. free any dynamically allocated content.
462 *
463 * @param pSettings The settings.
464 */
465static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
466{
467 if (pSettings)
468 {
469 Assert(pSettings->cchTab != ~(unsigned)0);
470 pSettings->cchTab = ~(unsigned)0;
471
472 RTStrFree(pSettings->pszFilterFiles);
473 pSettings->pszFilterFiles = NULL;
474
475 RTStrFree(pSettings->pszFilterOutFiles);
476 pSettings->pszFilterOutFiles = NULL;
477
478 RTStrFree(pSettings->pszFilterOutDirs);
479 pSettings->pszFilterOutDirs = NULL;
480 }
481}
482
483
484/**
485 * Processes a RTGetOpt result.
486 *
487 * @retval VINF_SUCCESS if handled.
488 * @retval VERR_OUT_OF_RANGE if the option value was out of range.
489 * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
490 *
491 * @param pSettings The settings to change.
492 * @param rc The RTGetOpt return value.
493 * @param pValueUnion The RTGetOpt value union.
494 */
495static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
496{
497 switch (rc)
498 {
499 case SCMOPT_CONVERT_EOL:
500 pSettings->fConvertEol = true;
501 return VINF_SUCCESS;
502 case SCMOPT_NO_CONVERT_EOL:
503 pSettings->fConvertEol = false;
504 return VINF_SUCCESS;
505
506 case SCMOPT_CONVERT_TABS:
507 pSettings->fConvertTabs = true;
508 return VINF_SUCCESS;
509 case SCMOPT_NO_CONVERT_TABS:
510 pSettings->fConvertTabs = false;
511 return VINF_SUCCESS;
512
513 case SCMOPT_FORCE_FINAL_EOL:
514 pSettings->fForceFinalEol = true;
515 return VINF_SUCCESS;
516 case SCMOPT_NO_FORCE_FINAL_EOL:
517 pSettings->fForceFinalEol = false;
518 return VINF_SUCCESS;
519
520 case SCMOPT_FORCE_TRAILING_LINE:
521 pSettings->fForceTrailingLine = true;
522 return VINF_SUCCESS;
523 case SCMOPT_NO_FORCE_TRAILING_LINE:
524 pSettings->fForceTrailingLine = false;
525 return VINF_SUCCESS;
526
527 case SCMOPT_STRIP_TRAILING_BLANKS:
528 pSettings->fStripTrailingBlanks = true;
529 return VINF_SUCCESS;
530 case SCMOPT_NO_STRIP_TRAILING_BLANKS:
531 pSettings->fStripTrailingBlanks = false;
532 return VINF_SUCCESS;
533
534 case SCMOPT_STRIP_TRAILING_LINES:
535 pSettings->fStripTrailingLines = true;
536 return VINF_SUCCESS;
537 case SCMOPT_NO_STRIP_TRAILING_LINES:
538 pSettings->fStripTrailingLines = false;
539 return VINF_SUCCESS;
540
541 case SCMOPT_ONLY_SVN_DIRS:
542 pSettings->fOnlySvnDirs = true;
543 return VINF_SUCCESS;
544 case SCMOPT_NOT_ONLY_SVN_DIRS:
545 pSettings->fOnlySvnDirs = false;
546 return VINF_SUCCESS;
547
548 case SCMOPT_ONLY_SVN_FILES:
549 pSettings->fOnlySvnFiles = true;
550 return VINF_SUCCESS;
551 case SCMOPT_NOT_ONLY_SVN_FILES:
552 pSettings->fOnlySvnFiles = false;
553 return VINF_SUCCESS;
554
555 case SCMOPT_SET_SVN_EOL:
556 pSettings->fSetSvnEol = true;
557 return VINF_SUCCESS;
558 case SCMOPT_DONT_SET_SVN_EOL:
559 pSettings->fSetSvnEol = false;
560 return VINF_SUCCESS;
561
562 case SCMOPT_SET_SVN_EXECUTABLE:
563 pSettings->fSetSvnExecutable = true;
564 return VINF_SUCCESS;
565 case SCMOPT_DONT_SET_SVN_EXECUTABLE:
566 pSettings->fSetSvnExecutable = false;
567 return VINF_SUCCESS;
568
569 case SCMOPT_SET_SVN_KEYWORDS:
570 pSettings->fSetSvnKeywords = true;
571 return VINF_SUCCESS;
572 case SCMOPT_DONT_SET_SVN_KEYWORDS:
573 pSettings->fSetSvnKeywords = false;
574 return VINF_SUCCESS;
575
576 case SCMOPT_TAB_SIZE:
577 if ( pValueUnion->u8 < 1
578 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
579 {
580 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
581 pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
582 return VERR_OUT_OF_RANGE;
583 }
584 pSettings->cchTab = pValueUnion->u8;
585 return VINF_SUCCESS;
586
587 case SCMOPT_FILTER_OUT_DIRS:
588 case SCMOPT_FILTER_FILES:
589 case SCMOPT_FILTER_OUT_FILES:
590 {
591 char **ppsz = NULL;
592 switch (rc)
593 {
594 case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
595 case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
596 case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
597 }
598
599 /*
600 * An empty string zaps the current list.
601 */
602 if (!*pValueUnion->psz)
603 return RTStrATruncate(ppsz, 0);
604
605 /*
606 * Non-empty strings are appended to the pattern list.
607 *
608 * Strip leading and trailing pattern separators before attempting
609 * to append it. If it's just separators, don't do anything.
610 */
611 const char *pszSrc = pValueUnion->psz;
612 while (*pszSrc == '|')
613 pszSrc++;
614 size_t cchSrc = strlen(pszSrc);
615 while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
616 cchSrc--;
617 if (!cchSrc)
618 return VINF_SUCCESS;
619
620 return RTStrAAppendExN(ppsz, 2,
621 "|", *ppsz && **ppsz ? 1 : 0,
622 pszSrc, cchSrc);
623 }
624
625 default:
626 return VERR_GETOPT_UNKNOWN_OPTION;
627 }
628}
629
630/**
631 * Parses an option string.
632 *
633 * @returns IPRT status code.
634 * @param pBase The base settings structure to apply the options
635 * to.
636 * @param pszOptions The options to parse.
637 */
638static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
639{
640 int cArgs;
641 char **papszArgs;
642 int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
643 if (RT_SUCCESS(rc))
644 {
645 RTGETOPTUNION ValueUnion;
646 RTGETOPTSTATE GetOptState;
647 rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
648 if (RT_SUCCESS(rc))
649 {
650 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
651 {
652 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
653 if (RT_FAILURE(rc))
654 break;
655 }
656 }
657 RTGetOptArgvFree(papszArgs);
658 }
659
660 return rc;
661}
662
663/**
664 * Parses an unterminated option string.
665 *
666 * @returns IPRT status code.
667 * @param pBase The base settings structure to apply the options
668 * to.
669 * @param pchLine The line.
670 * @param cchLine The line length.
671 */
672static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
673{
674 char *pszLine = RTStrDupN(pchLine, cchLine);
675 if (!pszLine)
676 return VERR_NO_MEMORY;
677 int rc = scmSettingsBaseParseString(pBase, pszLine);
678 RTStrFree(pszLine);
679 return rc;
680}
681
682/**
683 * Verifies the options string.
684 *
685 * @returns IPRT status code.
686 * @param pszOptions The options to verify .
687 */
688static int scmSettingsBaseVerifyString(const char *pszOptions)
689{
690 SCMSETTINGSBASE Base;
691 int rc = scmSettingsBaseInit(&Base);
692 if (RT_SUCCESS(rc))
693 {
694 rc = scmSettingsBaseParseString(&Base, pszOptions);
695 scmSettingsBaseDelete(&Base);
696 }
697 return rc;
698}
699
700/**
701 * Loads settings found in editor and SCM settings directives within the
702 * document (@a pStream).
703 *
704 * @returns IPRT status code.
705 * @param pBase The settings base to load settings into.
706 * @param pStream The stream to scan for settings directives.
707 */
708static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
709{
710 /** @todo Editor and SCM settings directives in documents. */
711 return VINF_SUCCESS;
712}
713
714/**
715 * Creates a new settings file struct, cloning @a pSettings.
716 *
717 * @returns IPRT status code.
718 * @param ppSettings Where to return the new struct.
719 * @param pSettingsBase The settings to inherit from.
720 */
721static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
722{
723 PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
724 if (!pSettings)
725 return VERR_NO_MEMORY;
726 int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
727 if (RT_SUCCESS(rc))
728 {
729 pSettings->pDown = NULL;
730 pSettings->pUp = NULL;
731 pSettings->paPairs = NULL;
732 pSettings->cPairs = 0;
733 *ppSettings = pSettings;
734 return VINF_SUCCESS;
735 }
736 RTMemFree(pSettings);
737 return rc;
738}
739
740/**
741 * Destroys a settings structure.
742 *
743 * @param pSettings The settings structure to destroy. NULL is OK.
744 */
745static void scmSettingsDestroy(PSCMSETTINGS pSettings)
746{
747 if (pSettings)
748 {
749 scmSettingsBaseDelete(&pSettings->Base);
750 for (size_t i = 0; i < pSettings->cPairs; i++)
751 {
752 RTStrFree(pSettings->paPairs[i].pszPattern);
753 RTStrFree(pSettings->paPairs[i].pszOptions);
754 pSettings->paPairs[i].pszPattern = NULL;
755 pSettings->paPairs[i].pszOptions = NULL;
756 }
757 RTMemFree(pSettings->paPairs);
758 pSettings->paPairs = NULL;
759 RTMemFree(pSettings);
760 }
761}
762
763/**
764 * Adds a pattern/options pair to the settings structure.
765 *
766 * @returns IPRT status code.
767 * @param pSettings The settings.
768 * @param pchLine The line containing the unparsed pair.
769 * @param cchLine The length of the line.
770 */
771static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
772{
773 /*
774 * Split the string.
775 */
776 const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
777 if (!pchOptions)
778 return VERR_INVALID_PARAMETER;
779 size_t cchPattern = pchOptions - pchLine;
780 size_t cchOptions = cchLine - cchPattern - 1;
781 pchOptions++;
782
783 /* strip spaces everywhere */
784 while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
785 cchPattern--;
786 while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
787 cchPattern--, pchLine++;
788
789 while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
790 cchOptions--;
791 while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
792 cchOptions--, pchOptions++;
793
794 /* Quietly ignore empty patterns and empty options. */
795 if (!cchOptions || !cchPattern)
796 return VINF_SUCCESS;
797
798 /*
799 * Add the pair and verify the option string.
800 */
801 uint32_t iPair = pSettings->cPairs;
802 if ((iPair % 32) == 0)
803 {
804 void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
805 if (!pvNew)
806 return VERR_NO_MEMORY;
807 pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
808 }
809
810 pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
811 pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
812 int rc;
813 if ( pSettings->paPairs[iPair].pszPattern
814 && pSettings->paPairs[iPair].pszOptions)
815 rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
816 else
817 rc = VERR_NO_MEMORY;
818 if (RT_SUCCESS(rc))
819 pSettings->cPairs = iPair + 1;
820 else
821 {
822 RTStrFree(pSettings->paPairs[iPair].pszPattern);
823 RTStrFree(pSettings->paPairs[iPair].pszOptions);
824 }
825 return rc;
826}
827
828/**
829 * Loads in the settings from @a pszFilename.
830 *
831 * @returns IPRT status code.
832 * @param pSettings Where to load the settings file.
833 * @param pszFilename The file to load.
834 */
835static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
836{
837 SCMSTREAM Stream;
838 int rc = ScmStreamInitForReading(&Stream, pszFilename);
839 if (RT_FAILURE(rc))
840 {
841 RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
842 return rc;
843 }
844
845 SCMEOL enmEol;
846 const char *pchLine;
847 size_t cchLine;
848 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
849 {
850 /* Ignore leading spaces. */
851 while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
852 pchLine++, cchLine--;
853
854 /* Ignore empty lines and comment lines. */
855 if (cchLine < 1 || *pchLine == '#')
856 continue;
857
858 /* What kind of line is it? */
859 const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
860 if (pchColon)
861 rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
862 else
863 rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
864 if (RT_FAILURE(rc))
865 {
866 RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
867 break;
868 }
869 }
870
871 if (RT_SUCCESS(rc))
872 {
873 rc = ScmStreamGetStatus(&Stream);
874 if (RT_FAILURE(rc))
875 RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
876 }
877
878 ScmStreamDelete(&Stream);
879 return rc;
880}
881
882/**
883 * Parse the specified settings file creating a new settings struct from it.
884 *
885 * @returns IPRT status code
886 * @param ppSettings Where to return the new settings.
887 * @param pszFilename The file to parse.
888 * @param pSettingsBase The base settings we inherit from.
889 */
890static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
891{
892 PSCMSETTINGS pSettings;
893 int rc = scmSettingsCreate(&pSettings, pSettingsBase);
894 if (RT_SUCCESS(rc))
895 {
896 rc = scmSettingsLoadFile(pSettings, pszFilename);
897 if (RT_SUCCESS(rc))
898 {
899 *ppSettings = pSettings;
900 return VINF_SUCCESS;
901 }
902
903 scmSettingsDestroy(pSettings);
904 }
905 *ppSettings = NULL;
906 return rc;
907}
908
909
910/**
911 * Create an initial settings structure when starting processing a new file or
912 * directory.
913 *
914 * This will look for .scm-settings files from the root and down to the
915 * specified directory, combining them into the returned settings structure.
916 *
917 * @returns IPRT status code.
918 * @param ppSettings Where to return the pointer to the top stack
919 * object.
920 * @param pBaseSettings The base settings we inherit from (globals
921 * typically).
922 * @param pszPath The absolute path to the new directory or file.
923 */
924static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
925{
926 *ppSettings = NULL; /* try shut up gcc. */
927
928 /*
929 * We'll be working with a stack copy of the path.
930 */
931 char szFile[RTPATH_MAX];
932 size_t cchDir = strlen(pszPath);
933 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
934 return VERR_FILENAME_TOO_LONG;
935
936 /*
937 * Create the bottom-most settings.
938 */
939 PSCMSETTINGS pSettings;
940 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
941 if (RT_FAILURE(rc))
942 return rc;
943
944 /*
945 * Enumerate the path components from the root and down. Load any setting
946 * files we find.
947 */
948 size_t cComponents = RTPathCountComponents(pszPath);
949 for (size_t i = 1; i <= cComponents; i++)
950 {
951 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
952 if (RT_SUCCESS(rc))
953 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
954 if (RT_FAILURE(rc))
955 break;
956 if (RTFileExists(szFile))
957 {
958 rc = scmSettingsLoadFile(pSettings, szFile);
959 if (RT_FAILURE(rc))
960 break;
961 }
962 }
963
964 if (RT_SUCCESS(rc))
965 *ppSettings = pSettings;
966 else
967 scmSettingsDestroy(pSettings);
968 return rc;
969}
970
971/**
972 * Pushes a new settings set onto the stack.
973 *
974 * @param ppSettingsStack The pointer to the pointer to the top stack
975 * element. This will be used as input and output.
976 * @param pSettings The settings to push onto the stack.
977 */
978static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
979{
980 PSCMSETTINGS pOld = *ppSettingsStack;
981 pSettings->pDown = pOld;
982 pSettings->pUp = NULL;
983 if (pOld)
984 pOld->pUp = pSettings;
985 *ppSettingsStack = pSettings;
986}
987
988/**
989 * Pushes the settings of the specified directory onto the stack.
990 *
991 * We will load any .scm-settings in the directory. A stack entry is added even
992 * if no settings file was found.
993 *
994 * @returns IPRT status code.
995 * @param ppSettingsStack The pointer to the pointer to the top stack
996 * element. This will be used as input and output.
997 * @param pszDir The directory to do this for.
998 */
999static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
1000{
1001 char szFile[RTPATH_MAX];
1002 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
1003 if (RT_SUCCESS(rc))
1004 {
1005 PSCMSETTINGS pSettings;
1006 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
1007 if (RT_SUCCESS(rc))
1008 {
1009 if (RTFileExists(szFile))
1010 rc = scmSettingsLoadFile(pSettings, szFile);
1011 if (RT_SUCCESS(rc))
1012 {
1013 scmSettingsStackPush(ppSettingsStack, pSettings);
1014 return VINF_SUCCESS;
1015 }
1016
1017 scmSettingsDestroy(pSettings);
1018 }
1019 }
1020 return rc;
1021}
1022
1023
1024/**
1025 * Pops a settings set off the stack.
1026 *
1027 * @returns The popped setttings.
1028 * @param ppSettingsStack The pointer to the pointer to the top stack
1029 * element. This will be used as input and output.
1030 */
1031static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
1032{
1033 PSCMSETTINGS pRet = *ppSettingsStack;
1034 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
1035 *ppSettingsStack = pNew;
1036 if (pNew)
1037 pNew->pUp = NULL;
1038 if (pRet)
1039 {
1040 pRet->pUp = NULL;
1041 pRet->pDown = NULL;
1042 }
1043 return pRet;
1044}
1045
1046/**
1047 * Pops and destroys the top entry of the stack.
1048 *
1049 * @param ppSettingsStack The pointer to the pointer to the top stack
1050 * element. This will be used as input and output.
1051 */
1052static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
1053{
1054 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
1055}
1056
1057/**
1058 * Constructs the base settings for the specified file name.
1059 *
1060 * @returns IPRT status code.
1061 * @param pSettingsStack The top element on the settings stack.
1062 * @param pszFilename The file name.
1063 * @param pszBasename The base name (pointer within @a pszFilename).
1064 * @param cchBasename The length of the base name. (For passing to
1065 * RTStrSimplePatternMultiMatch.)
1066 * @param pBase Base settings to initialize.
1067 */
1068static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
1069 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
1070{
1071 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
1072 if (RT_SUCCESS(rc))
1073 {
1074 /* find the bottom entry in the stack. */
1075 PCSCMSETTINGS pCur = pSettingsStack;
1076 while (pCur->pDown)
1077 pCur = pCur->pDown;
1078
1079 /* Work our way up thru the stack and look for matching pairs. */
1080 while (pCur)
1081 {
1082 size_t const cPairs = pCur->cPairs;
1083 if (cPairs)
1084 {
1085 for (size_t i = 0; i < cPairs; i++)
1086 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1087 pszBasename, cchBasename, NULL)
1088 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
1089 pszFilename, RTSTR_MAX, NULL))
1090 {
1091 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
1092 if (RT_FAILURE(rc))
1093 break;
1094 }
1095 if (RT_FAILURE(rc))
1096 break;
1097 }
1098
1099 /* advance */
1100 pCur = pCur->pUp;
1101 }
1102 }
1103 if (RT_FAILURE(rc))
1104 scmSettingsBaseDelete(pBase);
1105 return rc;
1106}
1107
1108
1109/* -=-=-=-=-=- misc -=-=-=-=-=- */
1110
1111
1112/**
1113 * Prints a verbose message if the level is high enough.
1114 *
1115 * @param pState The rewrite state. Optional.
1116 * @param iLevel The required verbosity level.
1117 * @param pszFormat The message format string. Can be NULL if we
1118 * only want to trigger the per file message.
1119 * @param ... Format arguments.
1120 */
1121static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
1122{
1123 if (iLevel <= g_iVerbosity)
1124 {
1125 if (pState && !pState->fFirst)
1126 {
1127 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
1128 pState->fFirst = true;
1129 }
1130 if (pszFormat)
1131 {
1132 RTPrintf(pState
1133 ? "%s: info: "
1134 : "%s: info: ",
1135 g_szProgName);
1136 va_list va;
1137 va_start(va, pszFormat);
1138 RTPrintfV(pszFormat, va);
1139 va_end(va);
1140 }
1141 }
1142}
1143
1144
1145/* -=-=-=-=-=- subversion -=-=-=-=-=- */
1146
1147#define SCM_WITHOUT_LIBSVN
1148
1149#ifdef SCM_WITHOUT_LIBSVN
1150
1151/**
1152 * Callback that is call for each path to search.
1153 */
1154static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
1155{
1156 char *pszDst = (char *)pvUser1;
1157 size_t cchDst = (size_t)pvUser2;
1158 if (cchDst > cchPath)
1159 {
1160 memcpy(pszDst, pchPath, cchPath);
1161 pszDst[cchPath] = '\0';
1162#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1163 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
1164#else
1165 int rc = RTPathAppend(pszDst, cchDst, "svn");
1166#endif
1167 if ( RT_SUCCESS(rc)
1168 && RTFileExists(pszDst))
1169 return VINF_SUCCESS;
1170 }
1171 return VERR_TRY_AGAIN;
1172}
1173
1174
1175/**
1176 * Finds the svn binary.
1177 *
1178 * @param pszPath Where to store it. Worst case, we'll return
1179 * "svn" here.
1180 * @param cchPath The size of the buffer pointed to by @a pszPath.
1181 */
1182static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
1183{
1184 /** @todo code page fun... */
1185 Assert(cchPath >= sizeof("svn"));
1186#ifdef RT_OS_WINDOWS
1187 const char *pszEnvVar = RTEnvGet("Path");
1188#else
1189 const char *pszEnvVar = RTEnvGet("PATH");
1190#endif
1191 if (pszPath)
1192 {
1193#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1194 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
1195#else
1196 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
1197#endif
1198 if (RT_SUCCESS(rc))
1199 return;
1200 }
1201 strcpy(pszPath, "svn");
1202}
1203
1204
1205/**
1206 * Construct a dot svn filename for the file being rewritten.
1207 *
1208 * @returns IPRT status code.
1209 * @param pState The rewrite state (for the name).
1210 * @param pszDir The directory, including ".svn/".
1211 * @param pszSuff The filename suffix.
1212 * @param pszDst The output buffer. RTPATH_MAX in size.
1213 */
1214static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
1215{
1216 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
1217 RTPathStripFilename(pszDst);
1218
1219 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
1220 if (RT_SUCCESS(rc))
1221 {
1222 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
1223 if (RT_SUCCESS(rc))
1224 {
1225 size_t cchDst = strlen(pszDst);
1226 size_t cchSuff = strlen(pszSuff);
1227 if (cchDst + cchSuff < RTPATH_MAX)
1228 {
1229 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
1230 return VINF_SUCCESS;
1231 }
1232 else
1233 rc = VERR_BUFFER_OVERFLOW;
1234 }
1235 }
1236 return rc;
1237}
1238
1239/**
1240 * Interprets the specified string as decimal numbers.
1241 *
1242 * @returns true if parsed successfully, false if not.
1243 * @param pch The string (not terminated).
1244 * @param cch The string length.
1245 * @param pu Where to return the value.
1246 */
1247static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
1248{
1249 size_t u = 0;
1250 while (cch-- > 0)
1251 {
1252 char ch = *pch++;
1253 if (ch < '0' || ch > '9')
1254 return false;
1255 u *= 10;
1256 u += ch - '0';
1257 }
1258 *pu = u;
1259 return true;
1260}
1261
1262#endif /* SCM_WITHOUT_LIBSVN */
1263
1264/**
1265 * Checks if the file we're operating on is part of a SVN working copy.
1266 *
1267 * @returns true if it is, false if it isn't or we cannot tell.
1268 * @param pState The rewrite state to work on.
1269 */
1270static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
1271{
1272#ifdef SCM_WITHOUT_LIBSVN
1273 /*
1274 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
1275 */
1276 char szPath[RTPATH_MAX];
1277 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
1278 if (RT_SUCCESS(rc))
1279 return RTFileExists(szPath);
1280
1281#else
1282 NOREF(pState);
1283#endif
1284 return false;
1285}
1286
1287/**
1288 * Queries the value of an SVN property.
1289 *
1290 * This will automatically adjust for scheduled changes.
1291 *
1292 * @returns IPRT status code.
1293 * @retval VERR_INVALID_STATE if not a SVN WC file.
1294 * @retval VERR_NOT_FOUND if the property wasn't found.
1295 * @param pState The rewrite state to work on.
1296 * @param pszName The property name.
1297 * @param ppszValue Where to return the property value. Free this
1298 * using RTStrFree. Optional.
1299 */
1300static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1301{
1302 /*
1303 * Look it up in the scheduled changes.
1304 */
1305 uint32_t i = pState->cSvnPropChanges;
1306 while (i-- > 0)
1307 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1308 {
1309 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1310 if (!pszValue)
1311 return VERR_NOT_FOUND;
1312 if (ppszValue)
1313 return RTStrDupEx(ppszValue, pszValue);
1314 return VINF_SUCCESS;
1315 }
1316
1317#ifdef SCM_WITHOUT_LIBSVN
1318 /*
1319 * Hack: Read the .svn/props/<file>.svn-work file exists.
1320 */
1321 char szPath[RTPATH_MAX];
1322 int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
1323 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
1324 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
1325 if (RT_SUCCESS(rc))
1326 {
1327 SCMSTREAM Stream;
1328 rc = ScmStreamInitForReading(&Stream, szPath);
1329 if (RT_SUCCESS(rc))
1330 {
1331 /*
1332 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
1333 */
1334 rc = VERR_NOT_FOUND;
1335 size_t const cchName = strlen(pszName);
1336 SCMEOL enmEol;
1337 size_t cchLine;
1338 const char *pchLine;
1339 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1340 {
1341 /*
1342 * Parse the 'K num' / 'END' line.
1343 */
1344 if ( cchLine == 3
1345 && !memcmp(pchLine, "END", 3))
1346 break;
1347 size_t cchKey;
1348 if ( cchLine < 3
1349 || pchLine[0] != 'K'
1350 || pchLine[1] != ' '
1351 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
1352 || cchKey == 0
1353 || cchKey > 4096)
1354 {
1355 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1356 rc = VERR_PARSE_ERROR;
1357 break;
1358 }
1359
1360 /*
1361 * Match the key and skip to the value line. Don't bother with
1362 * names containing EOL markers.
1363 */
1364 size_t const offKey = ScmStreamTell(&Stream);
1365 bool fMatch = cchName == cchKey;
1366 if (fMatch)
1367 {
1368 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1369 if (!pchLine)
1370 break;
1371 fMatch = cchLine == cchName
1372 && !memcmp(pchLine, pszName, cchName);
1373 }
1374
1375 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
1376 break;
1377 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1378 break;
1379
1380 /*
1381 * Read and Parse the 'V num' line.
1382 */
1383 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1384 if (!pchLine)
1385 break;
1386 size_t cchValue;
1387 if ( cchLine < 3
1388 || pchLine[0] != 'V'
1389 || pchLine[1] != ' '
1390 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
1391 || cchValue > _1M)
1392 {
1393 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1394 rc = VERR_PARSE_ERROR;
1395 break;
1396 }
1397
1398 /*
1399 * If we have a match, allocate a return buffer and read the
1400 * value into it. Otherwise skip this value and continue
1401 * searching.
1402 */
1403 if (fMatch)
1404 {
1405 if (!ppszValue)
1406 rc = VINF_SUCCESS;
1407 else
1408 {
1409 char *pszValue;
1410 rc = RTStrAllocEx(&pszValue, cchValue + 1);
1411 if (RT_SUCCESS(rc))
1412 {
1413 rc = ScmStreamRead(&Stream, pszValue, cchValue);
1414 if (RT_SUCCESS(rc))
1415 *ppszValue = pszValue;
1416 else
1417 RTStrFree(pszValue);
1418 }
1419 }
1420 break;
1421 }
1422
1423 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
1424 break;
1425 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1426 break;
1427 }
1428
1429 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
1430 {
1431 rc = ScmStreamGetStatus(&Stream);
1432 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
1433 }
1434 ScmStreamDelete(&Stream);
1435 }
1436 }
1437
1438 if (rc == VERR_FILE_NOT_FOUND)
1439 rc = VERR_NOT_FOUND;
1440 return rc;
1441
1442#else
1443 NOREF(pState);
1444#endif
1445 return VERR_NOT_FOUND;
1446}
1447
1448
1449/**
1450 * Schedules the setting of a property.
1451 *
1452 * @returns IPRT status code.
1453 * @retval VERR_INVALID_STATE if not a SVN WC file.
1454 * @param pState The rewrite state to work on.
1455 * @param pszName The name of the property to set.
1456 * @param pszValue The value. NULL means deleting it.
1457 */
1458static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
1459{
1460 /*
1461 * Update any existing entry first.
1462 */
1463 size_t i = pState->cSvnPropChanges;
1464 while (i-- > 0)
1465 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1466 {
1467 if (!pszValue)
1468 {
1469 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1470 pState->paSvnPropChanges[i].pszValue = NULL;
1471 }
1472 else
1473 {
1474 char *pszCopy;
1475 int rc = RTStrDupEx(&pszCopy, pszValue);
1476 if (RT_FAILURE(rc))
1477 return rc;
1478 pState->paSvnPropChanges[i].pszValue = pszCopy;
1479 }
1480 return VINF_SUCCESS;
1481 }
1482
1483 /*
1484 * Insert a new entry.
1485 */
1486 i = pState->cSvnPropChanges;
1487 if ((i % 32) == 0)
1488 {
1489 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
1490 if (!pvNew)
1491 return VERR_NO_MEMORY;
1492 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
1493 }
1494
1495 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
1496 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
1497 if ( pState->paSvnPropChanges[i].pszName
1498 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
1499 pState->cSvnPropChanges = i + 1;
1500 else
1501 {
1502 RTStrFree(pState->paSvnPropChanges[i].pszName);
1503 pState->paSvnPropChanges[i].pszName = NULL;
1504 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1505 pState->paSvnPropChanges[i].pszValue = NULL;
1506 return VERR_NO_MEMORY;
1507 }
1508 return VINF_SUCCESS;
1509}
1510
1511
1512/**
1513 * Schedules a property deletion.
1514 *
1515 * @returns IPRT status code.
1516 * @param pState The rewrite state to work on.
1517 * @param pszName The name of the property to delete.
1518 */
1519static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
1520{
1521 return scmSvnSetProperty(pState, pszName, NULL);
1522}
1523
1524
1525/**
1526 * Applies any SVN property changes to the work copy of the file.
1527 *
1528 * @returns IPRT status code.
1529 * @param pState The rewrite state which SVN property changes
1530 * should be applied.
1531 */
1532static int scmSvnDisplayChanges(PSCMRWSTATE pState)
1533{
1534 size_t i = pState->cSvnPropChanges;
1535 while (i-- > 0)
1536 {
1537 const char *pszName = pState->paSvnPropChanges[i].pszName;
1538 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1539 if (pszValue)
1540 ScmVerbose(pState, 0, "svn ps '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
1541 else
1542 ScmVerbose(pState, 0, "svn pd '%s' %s\n", pszName, pszValue, pState->pszFilename);
1543 }
1544
1545 return VINF_SUCCESS;
1546}
1547
1548/**
1549 * Applies any SVN property changes to the work copy of the file.
1550 *
1551 * @returns IPRT status code.
1552 * @param pState The rewrite state which SVN property changes
1553 * should be applied.
1554 */
1555static int scmSvnApplyChanges(PSCMRWSTATE pState)
1556{
1557#ifdef SCM_WITHOUT_LIBSVN
1558 /*
1559 * This sucks. We gotta find svn(.exe).
1560 */
1561 static char s_szSvnPath[RTPATH_MAX];
1562 if (s_szSvnPath[0] == '\0')
1563 scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
1564
1565 /*
1566 * Iterate thru the changes and apply them by starting the svn client.
1567 */
1568 for (size_t i = 0; i <pState->cSvnPropChanges; i++)
1569 {
1570 const char *apszArgv[6];
1571 apszArgv[0] = s_szSvnPath;
1572 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
1573 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
1574 int iArg = 3;
1575 if (pState->paSvnPropChanges[i].pszValue)
1576 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
1577 apszArgv[iArg++] = pState->pszFilename;
1578 apszArgv[iArg++] = NULL;
1579 ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
1580 apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
1581
1582 RTPROCESS pid;
1583 int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
1584 if (RT_SUCCESS(rc))
1585 {
1586 RTPROCSTATUS Status;
1587 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
1588 if ( RT_SUCCESS(rc)
1589 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
1590 || Status.iStatus != 0) )
1591 {
1592 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
1593 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
1594 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
1595 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
1596 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
1597 : "abducted by alien",
1598 Status.iStatus);
1599 return VERR_GENERAL_FAILURE;
1600 }
1601 }
1602 if (RT_FAILURE(rc))
1603 {
1604 RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
1605 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
1606 return rc;
1607 }
1608 }
1609
1610 return VINF_SUCCESS;
1611#else
1612 return VERR_NOT_IMPLEMENTED;
1613#endif
1614}
1615
1616
1617/* -=-=-=-=-=- rewriters -=-=-=-=-=- */
1618
1619
1620/**
1621 * Strip trailing blanks (space & tab).
1622 *
1623 * @returns True if modified, false if not.
1624 * @param pIn The input stream.
1625 * @param pOut The output stream.
1626 * @param pSettings The settings.
1627 */
1628static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1629{
1630 if (!pSettings->fStripTrailingBlanks)
1631 return false;
1632
1633 bool fModified = false;
1634 SCMEOL enmEol;
1635 size_t cchLine;
1636 const char *pchLine;
1637 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1638 {
1639 int rc;
1640 if ( cchLine == 0
1641 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
1642 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1643 else
1644 {
1645 cchLine--;
1646 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
1647 cchLine--;
1648 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1649 fModified = true;
1650 }
1651 if (RT_FAILURE(rc))
1652 return false;
1653 }
1654 if (fModified)
1655 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
1656 return fModified;
1657}
1658
1659/**
1660 * Expand tabs.
1661 *
1662 * @returns True if modified, false if not.
1663 * @param pIn The input stream.
1664 * @param pOut The output stream.
1665 * @param pSettings The settings.
1666 */
1667static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1668{
1669 if (!pSettings->fConvertTabs)
1670 return false;
1671
1672 size_t const cchTab = pSettings->cchTab;
1673 bool fModified = false;
1674 SCMEOL enmEol;
1675 size_t cchLine;
1676 const char *pchLine;
1677 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1678 {
1679 int rc;
1680 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
1681 if (!pchTab)
1682 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1683 else
1684 {
1685 size_t offTab = 0;
1686 const char *pchChunk = pchLine;
1687 for (;;)
1688 {
1689 size_t cchChunk = pchTab - pchChunk;
1690 offTab += cchChunk;
1691 ScmStreamWrite(pOut, pchChunk, cchChunk);
1692
1693 size_t cchToTab = cchTab - offTab % cchTab;
1694 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
1695 offTab += cchToTab;
1696
1697 pchChunk = pchTab + 1;
1698 size_t cchLeft = cchLine - (pchChunk - pchLine);
1699 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
1700 if (!pchTab)
1701 {
1702 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
1703 break;
1704 }
1705 }
1706
1707 fModified = true;
1708 }
1709 if (RT_FAILURE(rc))
1710 return false;
1711 }
1712 if (fModified)
1713 ScmVerbose(pState, 2, " * Expanded tabs\n");
1714 return fModified;
1715}
1716
1717/**
1718 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
1719 *
1720 * @returns true if modifications were made, false if not.
1721 * @param pIn The input stream.
1722 * @param pOut The output stream.
1723 * @param pSettings The settings.
1724 * @param enmDesiredEol The desired end of line indicator type.
1725 * @param pszDesiredSvnEol The desired svn:eol-style.
1726 */
1727static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1728 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
1729{
1730 if (!pSettings->fConvertEol)
1731 return false;
1732
1733 bool fModified = false;
1734 SCMEOL enmEol;
1735 size_t cchLine;
1736 const char *pchLine;
1737 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1738 {
1739 if ( enmEol != enmDesiredEol
1740 && enmEol != SCMEOL_NONE)
1741 {
1742 fModified = true;
1743 enmEol = enmDesiredEol;
1744 }
1745 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1746 if (RT_FAILURE(rc))
1747 return false;
1748 }
1749 if (fModified)
1750 ScmVerbose(pState, 2, " * Converted EOL markers\n");
1751
1752 /* Check svn:eol-style if appropriate */
1753 if ( pSettings->fSetSvnEol
1754 && scmSvnIsInWorkingCopy(pState))
1755 {
1756 char *pszEol;
1757 int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
1758 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
1759 || rc == VERR_NOT_FOUND)
1760 {
1761 if (rc == VERR_NOT_FOUND)
1762 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
1763 else
1764 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
1765 int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
1766 if (RT_FAILURE(rc2))
1767 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
1768 }
1769 if (RT_SUCCESS(rc))
1770 RTStrFree(pszEol);
1771 }
1772
1773 /** @todo also check the subversion svn:eol-style state! */
1774 return fModified;
1775}
1776
1777/**
1778 * Force native end of line indicator.
1779 *
1780 * @returns true if modifications were made, false if not.
1781 * @param pIn The input stream.
1782 * @param pOut The output stream.
1783 * @param pSettings The settings.
1784 */
1785static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1786{
1787#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
1788 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
1789#else
1790 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
1791#endif
1792}
1793
1794/**
1795 * Force the stream to use LF as the end of line indicator.
1796 *
1797 * @returns true if modifications were made, false if not.
1798 * @param pIn The input stream.
1799 * @param pOut The output stream.
1800 * @param pSettings The settings.
1801 */
1802static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1803{
1804 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
1805}
1806
1807/**
1808 * Force the stream to use CRLF as the end of line indicator.
1809 *
1810 * @returns true if modifications were made, false if not.
1811 * @param pIn The input stream.
1812 * @param pOut The output stream.
1813 * @param pSettings The settings.
1814 */
1815static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1816{
1817 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
1818}
1819
1820/**
1821 * Strip trailing blank lines and/or make sure there is exactly one blank line
1822 * at the end of the file.
1823 *
1824 * @returns true if modifications were made, false if not.
1825 * @param pIn The input stream.
1826 * @param pOut The output stream.
1827 * @param pSettings The settings.
1828 *
1829 * @remarks ASSUMES trailing white space has been removed already.
1830 */
1831static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1832{
1833 if ( !pSettings->fStripTrailingLines
1834 && !pSettings->fForceTrailingLine
1835 && !pSettings->fForceFinalEol)
1836 return false;
1837
1838 size_t const cLines = ScmStreamCountLines(pIn);
1839
1840 /* Empty files remains empty. */
1841 if (cLines <= 1)
1842 return false;
1843
1844 /* Figure out if we need to adjust the number of lines or not. */
1845 size_t cLinesNew = cLines;
1846
1847 if ( pSettings->fStripTrailingLines
1848 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
1849 {
1850 while ( cLinesNew > 1
1851 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
1852 cLinesNew--;
1853 }
1854
1855 if ( pSettings->fForceTrailingLine
1856 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
1857 cLinesNew++;
1858
1859 bool fFixMissingEol = pSettings->fForceFinalEol
1860 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
1861
1862 if ( !fFixMissingEol
1863 && cLines == cLinesNew)
1864 return false;
1865
1866 /* Copy the number of lines we've arrived at. */
1867 ScmStreamRewindForReading(pIn);
1868
1869 size_t cCopied = RT_MIN(cLinesNew, cLines);
1870 ScmStreamCopyLines(pOut, pIn, cCopied);
1871
1872 if (cCopied != cLinesNew)
1873 {
1874 while (cCopied++ < cLinesNew)
1875 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
1876 }
1877 /* Fix missing EOL if required. */
1878 else if (fFixMissingEol)
1879 {
1880 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
1881 ScmStreamWrite(pOut, "\n", 1);
1882 else
1883 ScmStreamWrite(pOut, "\r\n", 2);
1884 }
1885
1886 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
1887 return true;
1888}
1889
1890/**
1891 * Make sure there is no svn:executable keyword on the current file.
1892 *
1893 * @returns false - the state carries these kinds of changes.
1894 * @param pState The rewriter state.
1895 * @param pIn The input stream.
1896 * @param pOut The output stream.
1897 * @param pSettings The settings.
1898 */
1899static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1900{
1901 if ( !pSettings->fSetSvnExecutable
1902 || !scmSvnIsInWorkingCopy(pState))
1903 return false;
1904
1905 int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
1906 if (RT_SUCCESS(rc))
1907 {
1908 ScmVerbose(pState, 2, " * removing svn:executable\n");
1909 rc = scmSvnDelProperty(pState, "svn:executable");
1910 if (RT_FAILURE(rc))
1911 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
1912 }
1913 return false;
1914}
1915
1916/**
1917 * Make sure the Id and Revision keywords are expanded.
1918 *
1919 * @returns false - the state carries these kinds of changes.
1920 * @param pState The rewriter state.
1921 * @param pIn The input stream.
1922 * @param pOut The output stream.
1923 * @param pSettings The settings.
1924 */
1925static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1926{
1927 if ( !pSettings->fSetSvnKeywords
1928 || !scmSvnIsInWorkingCopy(pState))
1929 return false;
1930
1931 char *pszKeywords;
1932 int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
1933 if ( RT_SUCCESS(rc)
1934 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
1935 || !strstr(pszKeywords, "Revision")) )
1936 {
1937 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
1938 rc = RTStrAAppend(&pszKeywords, " Id Revision");
1939 else if (!strstr(pszKeywords, "Id"))
1940 rc = RTStrAAppend(&pszKeywords, " Id");
1941 else
1942 rc = RTStrAAppend(&pszKeywords, " Revision");
1943 if (RT_SUCCESS(rc))
1944 {
1945 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
1946 rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
1947 if (RT_FAILURE(rc))
1948 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
1949 }
1950 else
1951 RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
1952 RTStrFree(pszKeywords);
1953 }
1954 else if (rc == VERR_NOT_FOUND)
1955 {
1956 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
1957 rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
1958 if (RT_FAILURE(rc))
1959 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
1960 }
1961 else if (RT_SUCCESS(rc))
1962 RTStrFree(pszKeywords);
1963
1964 return false;
1965}
1966
1967/**
1968 * Makefile.kup are empty files, enforce this.
1969 *
1970 * @returns true if modifications were made, false if not.
1971 * @param pIn The input stream.
1972 * @param pOut The output stream.
1973 * @param pSettings The settings.
1974 */
1975static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1976{
1977 /* These files should be zero bytes. */
1978 if (pIn->cb == 0)
1979 return false;
1980 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1981 return true;
1982}
1983
1984/**
1985 * Rewrite a kBuild makefile.
1986 *
1987 * @returns true if modifications were made, false if not.
1988 * @param pIn The input stream.
1989 * @param pOut The output stream.
1990 * @param pSettings The settings.
1991 *
1992 * @todo
1993 *
1994 * Ideas for Makefile.kmk and Config.kmk:
1995 * - sort if1of/ifn1of sets.
1996 * - line continuation slashes should only be preceded by one space.
1997 */
1998static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1999{
2000 return false;
2001}
2002
2003/**
2004 * Rewrite a C/C++ source or header file.
2005 *
2006 * @returns true if modifications were made, false if not.
2007 * @param pIn The input stream.
2008 * @param pOut The output stream.
2009 * @param pSettings The settings.
2010 *
2011 * @todo
2012 *
2013 * Ideas for C/C++:
2014 * - space after if, while, for, switch
2015 * - spaces in for (i=0;i<x;i++)
2016 * - complex conditional, bird style.
2017 * - remove unnecessary parentheses.
2018 * - sort defined RT_OS_*|| and RT_ARCH
2019 * - sizeof without parenthesis.
2020 * - defined without parenthesis.
2021 * - trailing spaces.
2022 * - parameter indentation.
2023 * - space after comma.
2024 * - while (x--); -> multi line + comment.
2025 * - else statement;
2026 * - space between function and left parenthesis.
2027 * - TODO, XXX, @todo cleanup.
2028 * - Space before/after '*'.
2029 * - ensure new line at end of file.
2030 * - Indentation of precompiler statements (#ifdef, #defines).
2031 * - space between functions.
2032 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
2033 */
2034static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2035{
2036
2037 return false;
2038}
2039
2040/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
2041
2042/**
2043 * Processes a file.
2044 *
2045 * @returns IPRT status code.
2046 * @param pState The rewriter state.
2047 * @param pszFilename The file name.
2048 * @param pszBasename The base name (pointer within @a pszFilename).
2049 * @param cchBasename The length of the base name. (For passing to
2050 * RTStrSimplePatternMultiMatch.)
2051 * @param pBaseSettings The base settings to use. It's OK to modify
2052 * these.
2053 */
2054static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
2055 PSCMSETTINGSBASE pBaseSettings)
2056{
2057 /*
2058 * Do the file level filtering.
2059 */
2060 if ( pBaseSettings->pszFilterFiles
2061 && *pBaseSettings->pszFilterFiles
2062 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
2063 {
2064 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
2065 return VINF_SUCCESS;
2066 }
2067 if ( pBaseSettings->pszFilterOutFiles
2068 && *pBaseSettings->pszFilterOutFiles
2069 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
2070 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
2071 {
2072 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
2073 return VINF_SUCCESS;
2074 }
2075 if ( pBaseSettings->fOnlySvnFiles
2076 && !scmSvnIsInWorkingCopy(pState))
2077 {
2078 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
2079 return VINF_SUCCESS;
2080 }
2081
2082 /*
2083 * Try find a matching rewrite config for this filename.
2084 */
2085 PCSCMCFGENTRY pCfg = NULL;
2086 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
2087 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
2088 {
2089 pCfg = &g_aConfigs[iCfg];
2090 break;
2091 }
2092 if (!pCfg)
2093 {
2094 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
2095 return VINF_SUCCESS;
2096 }
2097 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
2098
2099 /*
2100 * Create an input stream from the file and check that it's text.
2101 */
2102 SCMSTREAM Stream1;
2103 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
2104 if (RT_FAILURE(rc))
2105 {
2106 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
2107 return rc;
2108 }
2109 if (ScmStreamIsText(&Stream1))
2110 {
2111 ScmVerbose(pState, 3, NULL);
2112
2113 /*
2114 * Gather SCM and editor settings from the stream.
2115 */
2116 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
2117 if (RT_SUCCESS(rc))
2118 {
2119 ScmStreamRewindForReading(&Stream1);
2120
2121 /*
2122 * Create two more streams for output and push the text thru all the
2123 * rewriters, switching the two streams around when something is
2124 * actually rewritten. Stream1 remains unchanged.
2125 */
2126 SCMSTREAM Stream2;
2127 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
2128 if (RT_SUCCESS(rc))
2129 {
2130 SCMSTREAM Stream3;
2131 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
2132 if (RT_SUCCESS(rc))
2133 {
2134 bool fModified = false;
2135 PSCMSTREAM pIn = &Stream1;
2136 PSCMSTREAM pOut = &Stream2;
2137 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
2138 {
2139 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
2140 if (fRc)
2141 {
2142 PSCMSTREAM pTmp = pOut;
2143 pOut = pIn == &Stream1 ? &Stream3 : pIn;
2144 pIn = pTmp;
2145 fModified = true;
2146 }
2147 ScmStreamRewindForReading(pIn);
2148 ScmStreamRewindForWriting(pOut);
2149 }
2150
2151 rc = ScmStreamGetStatus(&Stream1);
2152 if (RT_SUCCESS(rc))
2153 rc = ScmStreamGetStatus(&Stream2);
2154 if (RT_SUCCESS(rc))
2155 rc = ScmStreamGetStatus(&Stream3);
2156 if (RT_SUCCESS(rc))
2157 {
2158 /*
2159 * If rewritten, write it back to disk.
2160 */
2161 if (fModified)
2162 {
2163 if (!g_fDryRun)
2164 {
2165 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2166 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
2167 if (RT_FAILURE(rc))
2168 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
2169 }
2170 else
2171 {
2172 ScmVerbose(pState, 1, NULL);
2173 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
2174 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
2175 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
2176 }
2177 }
2178
2179 /*
2180 * If pending SVN property changes, apply them.
2181 */
2182 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
2183 {
2184 if (!g_fDryRun)
2185 {
2186 rc = scmSvnApplyChanges(pState);
2187 if (RT_FAILURE(rc))
2188 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
2189 }
2190 else
2191 scmSvnDisplayChanges(pState);
2192 }
2193
2194 if (!fModified && !pState->cSvnPropChanges)
2195 ScmVerbose(pState, 3, "no change\n", pszFilename);
2196 }
2197 else
2198 RTMsgError("%s: stream error %Rrc\n", pszFilename);
2199 ScmStreamDelete(&Stream3);
2200 }
2201 else
2202 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2203 ScmStreamDelete(&Stream2);
2204 }
2205 else
2206 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
2207 }
2208 else
2209 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
2210 }
2211 else
2212 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
2213 ScmStreamDelete(&Stream1);
2214
2215 return rc;
2216}
2217
2218/**
2219 * Processes a file.
2220 *
2221 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
2222 * directory recursion method.
2223 *
2224 * @returns IPRT status code.
2225 * @param pszFilename The file name.
2226 * @param pszBasename The base name (pointer within @a pszFilename).
2227 * @param cchBasename The length of the base name. (For passing to
2228 * RTStrSimplePatternMultiMatch.)
2229 * @param pSettingsStack The settings stack (pointer to the top element).
2230 */
2231static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
2232 PSCMSETTINGS pSettingsStack)
2233{
2234 SCMSETTINGSBASE Base;
2235 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
2236 if (RT_SUCCESS(rc))
2237 {
2238 SCMRWSTATE State;
2239 State.fFirst = false;
2240 State.pszFilename = pszFilename;
2241 State.cSvnPropChanges = 0;
2242 State.paSvnPropChanges = NULL;
2243
2244 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
2245
2246 size_t i = State.cSvnPropChanges;
2247 while (i-- > 0)
2248 {
2249 RTStrFree(State.paSvnPropChanges[i].pszName);
2250 RTStrFree(State.paSvnPropChanges[i].pszValue);
2251 }
2252 RTMemFree(State.paSvnPropChanges);
2253
2254 scmSettingsBaseDelete(&Base);
2255 }
2256 return rc;
2257}
2258
2259
2260/**
2261 * Tries to correct RTDIRENTRY_UNKNOWN.
2262 *
2263 * @returns Corrected type.
2264 * @param pszPath The path to the object in question.
2265 */
2266static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
2267{
2268 RTFSOBJINFO Info;
2269 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
2270 if (RT_FAILURE(rc))
2271 return RTDIRENTRYTYPE_UNKNOWN;
2272 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
2273 return RTDIRENTRYTYPE_DIRECTORY;
2274 if (RTFS_IS_FILE(Info.Attr.fMode))
2275 return RTDIRENTRYTYPE_FILE;
2276 return RTDIRENTRYTYPE_UNKNOWN;
2277}
2278
2279/**
2280 * Recurse into a sub-directory and process all the files and directories.
2281 *
2282 * @returns IPRT status code.
2283 * @param pszBuf Path buffer containing the directory path on
2284 * entry. This ends with a dot. This is passed
2285 * along when recursing in order to save stack space
2286 * and avoid needless copying.
2287 * @param cchDir Length of our path in pszbuf.
2288 * @param pEntry Directory entry buffer. This is also passed
2289 * along when recursing to save stack space.
2290 * @param pSettingsStack The settings stack (pointer to the top element).
2291 * @param iRecursion The recursion depth. This is used to restrict
2292 * the recursions.
2293 */
2294static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
2295 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
2296{
2297 int rc;
2298 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
2299
2300 /*
2301 * Make sure we stop somewhere.
2302 */
2303 if (iRecursion > 128)
2304 {
2305 RTMsgError("recursion too deep: %d\n", iRecursion);
2306 return VINF_SUCCESS; /* ignore */
2307 }
2308
2309 /*
2310 * Check if it's excluded by --only-svn-dir.
2311 */
2312 if (pSettingsStack->Base.fOnlySvnDirs)
2313 {
2314 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
2315 if (RT_FAILURE(rc))
2316 {
2317 RTMsgError("RTPathAppend: %Rrc\n", rc);
2318 return rc;
2319 }
2320 if (!RTDirExists(pszBuf))
2321 return VINF_SUCCESS;
2322
2323 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
2324 pszBuf[cchDir] = '\0';
2325 pszBuf[cchDir - 1] = '.';
2326 }
2327
2328 /*
2329 * Try open and read the directory.
2330 */
2331 PRTDIR pDir;
2332 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
2333 if (RT_FAILURE(rc))
2334 {
2335 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
2336 return rc;
2337 }
2338 for (;;)
2339 {
2340 /* Read the next entry. */
2341 rc = RTDirRead(pDir, pEntry, NULL);
2342 if (RT_FAILURE(rc))
2343 {
2344 if (rc == VERR_NO_MORE_FILES)
2345 rc = VINF_SUCCESS;
2346 else
2347 RTMsgError("RTDirRead -> %Rrc\n", rc);
2348 break;
2349 }
2350
2351 /* Skip '.' and '..'. */
2352 if ( pEntry->szName[0] == '.'
2353 && ( pEntry->cbName == 1
2354 || ( pEntry->cbName == 2
2355 && pEntry->szName[1] == '.')))
2356 continue;
2357
2358 /* Enter it into the buffer so we've got a full name to work
2359 with when needed. */
2360 if (pEntry->cbName + cchDir >= RTPATH_MAX)
2361 {
2362 RTMsgError("Skipping too long entry: %s", pEntry->szName);
2363 continue;
2364 }
2365 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
2366
2367 /* Figure the type. */
2368 RTDIRENTRYTYPE enmType = pEntry->enmType;
2369 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
2370 enmType = scmFigureUnknownType(pszBuf);
2371
2372 /* Process the file or directory, skip the rest. */
2373 if (enmType == RTDIRENTRYTYPE_FILE)
2374 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
2375 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
2376 {
2377 /* Append the dot for the benefit of the pattern matching. */
2378 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
2379 {
2380 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
2381 continue;
2382 }
2383 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
2384 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
2385
2386 if ( !pSettingsStack->Base.pszFilterOutDirs
2387 || !*pSettingsStack->Base.pszFilterOutDirs
2388 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2389 pEntry->szName, pEntry->cbName, NULL)
2390 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
2391 pszBuf, cchSubDir, NULL)
2392 )
2393 )
2394 {
2395 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
2396 if (RT_SUCCESS(rc))
2397 {
2398 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
2399 scmSettingsStackPopAndDestroy(&pSettingsStack);
2400 }
2401 }
2402 }
2403 if (RT_FAILURE(rc))
2404 break;
2405 }
2406 RTDirClose(pDir);
2407 return rc;
2408
2409}
2410
2411/**
2412 * Process a directory tree.
2413 *
2414 * @returns IPRT status code.
2415 * @param pszDir The directory to start with. This is pointer to
2416 * a RTPATH_MAX sized buffer.
2417 */
2418static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
2419{
2420 /*
2421 * Setup the recursion.
2422 */
2423 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
2424 if (RT_SUCCESS(rc))
2425 {
2426 RTDIRENTRY Entry;
2427 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
2428 }
2429 else
2430 RTMsgError("RTPathAppend: %Rrc\n", rc);
2431 return rc;
2432}
2433
2434
2435/**
2436 * Processes a file or directory specified as an command line argument.
2437 *
2438 * @returns IPRT status code
2439 * @param pszSomething What we found in the command line arguments.
2440 * @param pSettingsStack The settings stack (pointer to the top element).
2441 */
2442static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
2443{
2444 char szBuf[RTPATH_MAX];
2445 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
2446 if (RT_SUCCESS(rc))
2447 {
2448 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
2449
2450 PSCMSETTINGS pSettings;
2451 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
2452 if (RT_SUCCESS(rc))
2453 {
2454 scmSettingsStackPush(&pSettingsStack, pSettings);
2455
2456 if (RTFileExists(szBuf))
2457 {
2458 const char *pszBasename = RTPathFilename(szBuf);
2459 if (pszBasename)
2460 {
2461 size_t cchBasename = strlen(pszBasename);
2462 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
2463 }
2464 else
2465 {
2466 RTMsgError("RTPathFilename: NULL\n");
2467 rc = VERR_IS_A_DIRECTORY;
2468 }
2469 }
2470 else
2471 rc = scmProcessDirTree(szBuf, pSettingsStack);
2472
2473 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
2474 Assert(pPopped == pSettings);
2475 scmSettingsDestroy(pSettings);
2476 }
2477 else
2478 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
2479 }
2480 else
2481 RTMsgError("RTPathAbs: %Rrc\n", rc);
2482 return rc;
2483}
2484
2485int main(int argc, char **argv)
2486{
2487 int rc = RTR3InitExe(argc, &argv, 0);
2488 if (RT_FAILURE(rc))
2489 return 1;
2490
2491 /*
2492 * Init the settings.
2493 */
2494 PSCMSETTINGS pSettings;
2495 rc = scmSettingsCreate(&pSettings, &g_Defaults);
2496 if (RT_FAILURE(rc))
2497 {
2498 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
2499 return 1;
2500 }
2501
2502 /*
2503 * Parse arguments and process input in order (because this is the only
2504 * thing that works at the moment).
2505 */
2506 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
2507 {
2508 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
2509 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
2510 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
2511 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2512 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2513 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2514 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
2515 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2516 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
2517 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2518 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
2519 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2520 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
2521 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2522 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
2523 };
2524 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
2525
2526 RTGETOPTUNION ValueUnion;
2527 RTGETOPTSTATE GetOptState;
2528 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
2529 AssertReleaseRCReturn(rc, 1);
2530 size_t cProcessed = 0;
2531
2532 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
2533 {
2534 switch (rc)
2535 {
2536 case 'd':
2537 g_fDryRun = true;
2538 break;
2539 case 'D':
2540 g_fDryRun = false;
2541 break;
2542
2543 case 'f':
2544 g_pszFileFilter = ValueUnion.psz;
2545 break;
2546
2547 case 'h':
2548 RTPrintf("VirtualBox Source Code Massager\n"
2549 "\n"
2550 "Usage: %s [options] <files & dirs>\n"
2551 "\n"
2552 "Options:\n", g_szProgName);
2553 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
2554 {
2555 bool fAdvanceTwo = false;
2556 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
2557 {
2558 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
2559 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
2560 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
2561 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
2562 );
2563 if (fAdvanceTwo)
2564 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
2565 else
2566 RTPrintf(" %s\n", s_aOpts[i].pszLong);
2567 }
2568 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
2569 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
2570 else
2571 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
2572 switch (s_aOpts[i].iShort)
2573 {
2574 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
2575 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
2576 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
2577 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
2578 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
2579 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
2580 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
2581 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
2582 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
2583 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
2584 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
2585 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
2586 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
2587 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
2588 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
2589 }
2590 i += fAdvanceTwo;
2591 }
2592 return 1;
2593
2594 case 'q':
2595 g_iVerbosity = 0;
2596 break;
2597
2598 case 'v':
2599 g_iVerbosity++;
2600 break;
2601
2602 case 'V':
2603 {
2604 /* The following is assuming that svn does it's job here. */
2605 static const char s_szRev[] = "$Revision: 40530 $";
2606 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
2607 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
2608 return 0;
2609 }
2610
2611 case SCMOPT_DIFF_IGNORE_EOL:
2612 g_fDiffIgnoreEol = true;
2613 break;
2614 case SCMOPT_DIFF_NO_IGNORE_EOL:
2615 g_fDiffIgnoreEol = false;
2616 break;
2617
2618 case SCMOPT_DIFF_IGNORE_SPACE:
2619 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
2620 break;
2621 case SCMOPT_DIFF_NO_IGNORE_SPACE:
2622 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
2623 break;
2624
2625 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
2626 g_fDiffIgnoreLeadingWS = true;
2627 break;
2628 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
2629 g_fDiffIgnoreLeadingWS = false;
2630 break;
2631
2632 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
2633 g_fDiffIgnoreTrailingWS = true;
2634 break;
2635 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
2636 g_fDiffIgnoreTrailingWS = false;
2637 break;
2638
2639 case SCMOPT_DIFF_SPECIAL_CHARS:
2640 g_fDiffSpecialChars = true;
2641 break;
2642 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
2643 g_fDiffSpecialChars = false;
2644 break;
2645
2646 case VINF_GETOPT_NOT_OPTION:
2647 {
2648 if (!g_fDryRun)
2649 {
2650 if (!cProcessed)
2651 {
2652 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
2653 "%s: there is a slight risk that bugs or a full disk may cause\n"
2654 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
2655 "%s: all your changes already. If you didn't, then don't blame\n"
2656 "%s: anyone for not warning you!\n"
2657 "%s:\n"
2658 "%s: Press any key to continue...\n",
2659 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
2660 g_szProgName, g_szProgName);
2661 RTStrmGetCh(g_pStdIn);
2662 }
2663 cProcessed++;
2664 }
2665 rc = scmProcessSomething(ValueUnion.psz, pSettings);
2666 if (RT_FAILURE(rc))
2667 return rc;
2668 break;
2669 }
2670
2671 default:
2672 {
2673 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
2674 if (RT_SUCCESS(rc2))
2675 break;
2676 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
2677 return 2;
2678 return RTGetOptPrintError(rc, &ValueUnion);
2679 }
2680 }
2681 }
2682
2683 scmSettingsDestroy(pSettings);
2684 return 0;
2685}
2686
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