- Timestamp:
- Mar 19, 2012 11:49:34 AM (13 years ago)
- Location:
- trunk/src/bldprogs
- Files:
-
- 1 added
- 2 edited
- 2 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/bldprogs/Makefile.kmk
r40530 r40534 38 38 scm_SOURCES = \ 39 39 scm.cpp \ 40 scmdiff.cpp \ 41 scmrw.cpp \ 40 42 scmstream.cpp \ 41 scm diff.cpp43 scmsubversion.cpp 42 44 scm_LIBS = \ 43 45 $(LIB_RUNTIME) -
trunk/src/bldprogs/scm.cpp
r40530 r40534 35 35 #include <iprt/string.h> 36 36 37 #include "scm stream.h"37 #include "scm.h" 38 38 #include "scmdiff.h" 39 39 … … 49 49 * Structures and Typedefs * 50 50 *******************************************************************************/ 51 /** Pointer to const massager settings. */52 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;53 54 /**55 * SVN property.56 */57 typedef struct SCMSVNPROP58 {59 /** The property. */60 char *pszName;61 /** The value.62 * When used to record updates, this can be set to NULL to trigger the63 * deletion of the property. */64 char *pszValue;65 } SCMSVNPROP;66 /** Pointer to a SVN property. */67 typedef SCMSVNPROP *PSCMSVNPROP;68 /** Pointer to a const SVN property. */69 typedef SCMSVNPROP const *PCSCMSVNPROP;70 71 72 /**73 * Rewriter state.74 */75 typedef struct SCMRWSTATE76 {77 /** The filename. */78 const char *pszFilename;79 /** Set after the printing the first verbose message about a file under80 * 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. */88 typedef SCMRWSTATE *PSCMRWSTATE;89 90 /**91 * A rewriter.92 *93 * This works like a stream editor, reading @a pIn, modifying it and writing it94 * 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 */101 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);102 103 104 /**105 * Configuration entry.106 */107 typedef struct SCMCFGENTRY108 {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;116 typedef SCMCFGENTRY *PSCMCFGENTRY;117 typedef SCMCFGENTRY const *PCSCMCFGENTRY;118 119 120 /**121 * Source Code Massager Settings.122 */123 typedef struct SCMSETTINGSBASE124 {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 the144 * base names. */145 char *pszFilterFiles;146 /** Filter out files matching the following patterns. This is applied to base147 * names as well as the absolute paths. */148 char *pszFilterOutFiles;149 /** Filter out directories matching the following patterns. This is applied150 * to base names as well as the absolute paths. All absolute paths ends with a151 * slash and dot ("/."). */152 char *pszFilterOutDirs;153 } SCMSETTINGSBASE;154 /** Pointer to massager settings. */155 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;156 51 157 52 /** … … 207 102 208 103 209 /**210 * File/dir pattern + options.211 */212 typedef struct SCMPATRNOPTPAIR213 {214 char *pszPattern;215 char *pszOptions;216 } SCMPATRNOPTPAIR;217 /** Pointer to a pattern + option pair. */218 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;219 220 221 /** Pointer to a settings set. */222 typedef struct SCMSETTINGS *PSCMSETTINGS;223 /**224 * Settings set.225 *226 * This structure is constructed from the command line arguments or any227 * .scm-settings file found in a directory we recurse into. When recursing in228 * 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 the231 * unqualified base settings and then there are the settings which applies to a232 * 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 applies235 * to.236 *237 * We parse the base options into the Base member and put the others into the238 * paPairs array.239 */240 typedef struct SCMSETTINGS241 {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. */254 typedef SCMSETTINGS const *PCSCMSETTINGS;255 256 257 /*******************************************************************************258 * Internal Functions *259 *******************************************************************************/260 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);261 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);262 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);263 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);264 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);265 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);266 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);267 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);268 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);269 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);270 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);271 272 273 104 /******************************************************************************* 274 105 * Global Variables * 275 106 *******************************************************************************/ 107 const char g_szTabSpaces[16+1] = " "; 276 108 static const char g_szProgName[] = "scm"; 277 109 static const char *g_pszChangedSuff = ""; 278 static const char g_szTabSpaces[16+1] = " ";279 110 static bool g_fDryRun = true; 280 111 static bool g_fDiffSpecialChars = true; … … 1119 950 * @param ... Format arguments. 1120 951 */ 1121 staticvoid ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)952 void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...) 1122 953 { 1123 954 if (iLevel <= g_iVerbosity) … … 1143 974 1144 975 1145 /* -=-=-=-=-=- subversion -=-=-=-=-=- */1146 1147 #define SCM_WITHOUT_LIBSVN1148 1149 #ifdef SCM_WITHOUT_LIBSVN1150 1151 /**1152 * Callback that is call for each path to search.1153 */1154 static 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 #else1165 int rc = RTPathAppend(pszDst, cchDst, "svn");1166 #endif1167 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 return1179 * "svn" here.1180 * @param cchPath The size of the buffer pointed to by @a pszPath.1181 */1182 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)1183 {1184 /** @todo code page fun... */1185 Assert(cchPath >= sizeof("svn"));1186 #ifdef RT_OS_WINDOWS1187 const char *pszEnvVar = RTEnvGet("Path");1188 #else1189 const char *pszEnvVar = RTEnvGet("PATH");1190 #endif1191 if (pszPath)1192 {1193 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)1194 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1195 #else1196 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);1197 #endif1198 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 */1214 static 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 else1233 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 */1247 static 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 */1270 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)1271 {1272 #ifdef SCM_WITHOUT_LIBSVN1273 /*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 #else1282 NOREF(pState);1283 #endif1284 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 this1298 * using RTStrFree. Optional.1299 */1300 static 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_LIBSVN1318 /*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 == 31345 && !memcmp(pchLine, "END", 3))1346 break;1347 size_t cchKey;1348 if ( cchLine < 31349 || pchLine[0] != 'K'1350 || pchLine[1] != ' '1351 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)1352 || cchKey == 01353 || 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 with1362 * 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 == cchName1372 && !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 < 31388 || 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 the1400 * value into it. Otherwise skip this value and continue1401 * searching.1402 */1403 if (fMatch)1404 {1405 if (!ppszValue)1406 rc = VINF_SUCCESS;1407 else1408 {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 else1417 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 #else1443 NOREF(pState);1444 #endif1445 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 */1458 static 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 else1473 {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].pszName1498 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )1499 pState->cSvnPropChanges = i + 1;1500 else1501 {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 */1519 static 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 changes1530 * should be applied.1531 */1532 static 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 else1542 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 changes1553 * should be applied.1554 */1555 static int scmSvnApplyChanges(PSCMRWSTATE pState)1556 {1557 #ifdef SCM_WITHOUT_LIBSVN1558 /*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_NORMAL1590 || 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 #else1612 return VERR_NOT_IMPLEMENTED;1613 #endif1614 }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 */1628 static 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 == 01641 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )1642 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);1643 else1644 {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 */1667 static 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 else1684 {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 */1727 static 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 != enmDesiredEol1740 && 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->fSetSvnEol1754 && 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 else1764 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 */1785 static 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 #else1790 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");1791 #endif1792 }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 */1802 static 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 */1815 static 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 line1822 * 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 */1831 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1832 {1833 if ( !pSettings->fStripTrailingLines1834 && !pSettings->fForceTrailingLine1835 && !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->fStripTrailingLines1848 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))1849 {1850 while ( cLinesNew > 11851 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))1852 cLinesNew--;1853 }1854 1855 if ( pSettings->fForceTrailingLine1856 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))1857 cLinesNew++;1858 1859 bool fFixMissingEol = pSettings->fForceFinalEol1860 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;1861 1862 if ( !fFixMissingEol1863 && 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 else1883 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 */1899 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1900 {1901 if ( !pSettings->fSetSvnExecutable1902 || !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 */1925 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)1926 {1927 if ( !pSettings->fSetSvnKeywords1928 || !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 else1942 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 else1951 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 */1975 static 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 * @todo1993 *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 */1998 static 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 * @todo2012 *2013 * Ideas for C/C++:2014 * - space after if, while, for, switch2015 * - spaces in for (i=0;i<x;i++)2016 * - complex conditional, bird style.2017 * - remove unnecessary parentheses.2018 * - sort defined RT_OS_*|| and RT_ARCH2019 * - 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 */2034 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)2035 {2036 2037 return false;2038 }2039 2040 976 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */ 977 2041 978 2042 979 /** … … 2074 1011 } 2075 1012 if ( pBaseSettings->fOnlySvnFiles 2076 && ! scmSvnIsInWorkingCopy(pState))1013 && !ScmSvnIsInWorkingCopy(pState)) 2077 1014 { 2078 1015 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename); … … 2184 1121 if (!g_fDryRun) 2185 1122 { 2186 rc = scmSvnApplyChanges(pState);1123 rc = ScmSvnApplyChanges(pState); 2187 1124 if (RT_FAILURE(rc)) 2188 1125 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc); 2189 1126 } 2190 1127 else 2191 scmSvnDisplayChanges(pState);1128 ScmSvnDisplayChanges(pState); 2192 1129 } 2193 1130 -
trunk/src/bldprogs/scmrw.cpp
r40530 r40534 35 35 #include <iprt/string.h> 36 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. */ 52 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; 53 54 /** 55 * SVN property. 56 */ 57 typedef 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. */ 67 typedef SCMSVNPROP *PSCMSVNPROP; 68 /** Pointer to a const SVN property. */ 69 typedef SCMSVNPROP const *PCSCMSVNPROP; 70 71 72 /** 73 * Rewriter state. 74 */ 75 typedef 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. */ 88 typedef 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 */ 101 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 102 103 104 /** 105 * Configuration entry. 106 */ 107 typedef 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; 116 typedef SCMCFGENTRY *PSCMCFGENTRY; 117 typedef SCMCFGENTRY const *PCSCMCFGENTRY; 118 119 120 /** 121 * Source Code Massager Settings. 122 */ 123 typedef 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. */ 155 typedef 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 */ 165 typedef 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 */ 212 typedef struct SCMPATRNOPTPAIR 213 { 214 char *pszPattern; 215 char *pszOptions; 216 } SCMPATRNOPTPAIR; 217 /** Pointer to a pattern + option pair. */ 218 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR; 219 220 221 /** Pointer to a settings set. */ 222 typedef 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 */ 240 typedef 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. */ 254 typedef SCMSETTINGS const *PCSCMSETTINGS; 255 256 257 /******************************************************************************* 258 * Internal Functions * 259 *******************************************************************************/ 260 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 261 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 262 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 263 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 264 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 265 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 266 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 267 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 268 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 269 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 270 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 271 272 273 /******************************************************************************* 274 * Global Variables * 275 *******************************************************************************/ 276 static const char g_szProgName[] = "scm"; 277 static const char *g_pszChangedSuff = ""; 278 static const char g_szTabSpaces[16+1] = " "; 279 static bool g_fDryRun = true; 280 static bool g_fDiffSpecialChars = true; 281 static bool g_fDiffIgnoreEol = false; 282 static bool g_fDiffIgnoreLeadingWS = false; 283 static bool g_fDiffIgnoreTrailingWS = false; 284 static int g_iVerbosity = 2;//99; //0; 285 286 /** The global settings. */ 287 static 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. */ 307 static 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). */ 338 static const char *g_pszFileFilter = NULL; 339 340 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] = 341 { 342 rewrite_SvnNoExecutable, 343 rewrite_Makefile_kup 344 }; 345 346 static 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 356 static 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 367 static 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 377 static 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 387 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] = 388 { 389 rewrite_ForceLF, 390 rewrite_ExpandTabs, 391 rewrite_StripTrailingBlanks 392 }; 393 394 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] = 395 { 396 rewrite_ForceCRLF, 397 rewrite_ExpandTabs, 398 rewrite_StripTrailingBlanks 399 }; 400 401 static 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 */ 424 static 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 */ 455 static 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 */ 465 static 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 */ 495 static 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 */ 638 static 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 */ 672 static 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 */ 688 static 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 */ 708 static 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 */ 721 static 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 */ 745 static 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 */ 771 static 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 */ 835 static 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 */ 890 static 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 */ 924 static 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 */ 978 static 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 */ 999 static 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 */ 1031 static 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 */ 1052 static 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 */ 1068 static 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 */ 1121 static 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 */ 1154 static 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 */ 1182 static 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 */ 1214 static 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 */ 1247 static 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 */ 1270 static 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 */ 1300 static 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 */ 1458 static 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 */ 1519 static 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 */ 1532 static 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 */ 1555 static 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 -=-=-=-=-=- */ 37 #include "scm.h" 38 1618 39 1619 40 … … 1626 47 * @param pSettings The settings. 1627 48 */ 1628 staticbool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)49 bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1629 50 { 1630 51 if (!pSettings->fStripTrailingBlanks) … … 1665 86 * @param pSettings The settings. 1666 87 */ 1667 staticbool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)88 bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1668 89 { 1669 90 if (!pSettings->fConvertTabs) … … 1752 173 /* Check svn:eol-style if appropriate */ 1753 174 if ( pSettings->fSetSvnEol 1754 && scmSvnIsInWorkingCopy(pState))175 && ScmSvnIsInWorkingCopy(pState)) 1755 176 { 1756 177 char *pszEol; 1757 int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);178 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol); 1758 179 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol)) 1759 180 || rc == VERR_NOT_FOUND) … … 1763 184 else 1764 185 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol); 1765 int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);186 int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol); 1766 187 if (RT_FAILURE(rc2)) 1767 RTMsgError(" scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */188 RTMsgError("ScmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */ 1768 189 } 1769 190 if (RT_SUCCESS(rc)) … … 1783 204 * @param pSettings The settings. 1784 205 */ 1785 staticbool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)206 bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1786 207 { 1787 208 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) … … 1800 221 * @param pSettings The settings. 1801 222 */ 1802 staticbool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)223 bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1803 224 { 1804 225 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF"); … … 1813 234 * @param pSettings The settings. 1814 235 */ 1815 staticbool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)236 bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1816 237 { 1817 238 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF"); … … 1829 250 * @remarks ASSUMES trailing white space has been removed already. 1830 251 */ 1831 staticbool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)252 bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1832 253 { 1833 254 if ( !pSettings->fStripTrailingLines … … 1897 318 * @param pSettings The settings. 1898 319 */ 1899 staticbool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)320 bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1900 321 { 1901 322 if ( !pSettings->fSetSvnExecutable 1902 || ! scmSvnIsInWorkingCopy(pState))1903 return false; 1904 1905 int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);323 || !ScmSvnIsInWorkingCopy(pState)) 324 return false; 325 326 int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL); 1906 327 if (RT_SUCCESS(rc)) 1907 328 { 1908 329 ScmVerbose(pState, 2, " * removing svn:executable\n"); 1909 rc = scmSvnDelProperty(pState, "svn:executable");330 rc = ScmSvnDelProperty(pState, "svn:executable"); 1910 331 if (RT_FAILURE(rc)) 1911 RTMsgError(" scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */332 RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */ 1912 333 } 1913 334 return false; … … 1923 344 * @param pSettings The settings. 1924 345 */ 1925 staticbool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)346 bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1926 347 { 1927 348 if ( !pSettings->fSetSvnKeywords 1928 || ! scmSvnIsInWorkingCopy(pState))349 || !ScmSvnIsInWorkingCopy(pState)) 1929 350 return false; 1930 351 1931 352 char *pszKeywords; 1932 int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);353 int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords); 1933 354 if ( RT_SUCCESS(rc) 1934 355 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */ … … 1944 365 { 1945 366 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords); 1946 rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);367 rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords); 1947 368 if (RT_FAILURE(rc)) 1948 RTMsgError(" scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */369 RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */ 1949 370 } 1950 371 else … … 1955 376 { 1956 377 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n"); 1957 rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");378 rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision"); 1958 379 if (RT_FAILURE(rc)) 1959 RTMsgError(" scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */380 RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */ 1960 381 } 1961 382 else if (RT_SUCCESS(rc)) … … 1973 394 * @param pSettings The settings. 1974 395 */ 1975 staticbool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)396 bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1976 397 { 1977 398 /* These files should be zero bytes. */ … … 1996 417 * - line continuation slashes should only be preceded by one space. 1997 418 */ 1998 staticbool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)419 bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 1999 420 { 2000 421 return false; … … 2032 453 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc. 2033 454 */ 2034 staticbool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)455 bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings) 2035 456 { 2036 457 … … 2038 459 } 2039 460 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 to2050 * RTStrSimplePatternMultiMatch.)2051 * @param pBaseSettings The base settings to use. It's OK to modify2052 * these.2053 */2054 static 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->pszFilterFiles2061 && *pBaseSettings->pszFilterFiles2062 && !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->pszFilterOutFiles2068 && *pBaseSettings->pszFilterOutFiles2069 && ( 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->fOnlySvnFiles2076 && !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 the2123 * rewriters, switching the two streams around when something is2124 * 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 else2171 {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 else2191 scmSvnDisplayChanges(pState);2192 }2193 2194 if (!fModified && !pState->cSvnPropChanges)2195 ScmVerbose(pState, 3, "no change\n", pszFilename);2196 }2197 else2198 RTMsgError("%s: stream error %Rrc\n", pszFilename);2199 ScmStreamDelete(&Stream3);2200 }2201 else2202 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2203 ScmStreamDelete(&Stream2);2204 }2205 else2206 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);2207 }2208 else2209 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);2210 }2211 else2212 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 the2222 * 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 to2228 * RTStrSimplePatternMultiMatch.)2229 * @param pSettingsStack The settings stack (pointer to the top element).2230 */2231 static 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 */2266 static 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 on2284 * entry. This ends with a dot. This is passed2285 * along when recursing in order to save stack space2286 * and avoid needless copying.2287 * @param cchDir Length of our path in pszbuf.2288 * @param pEntry Directory entry buffer. This is also passed2289 * 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 restrict2292 * the recursions.2293 */2294 static 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 else2347 RTMsgError("RTDirRead -> %Rrc\n", rc);2348 break;2349 }2350 2351 /* Skip '.' and '..'. */2352 if ( pEntry->szName[0] == '.'2353 && ( pEntry->cbName == 12354 || ( pEntry->cbName == 22355 && pEntry->szName[1] == '.')))2356 continue;2357 2358 /* Enter it into the buffer so we've got a full name to work2359 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.pszFilterOutDirs2387 || !*pSettingsStack->Base.pszFilterOutDirs2388 || ( !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 to2416 * a RTPATH_MAX sized buffer.2417 */2418 static 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 else2430 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 code2439 * @param pszSomething What we found in the command line arguments.2440 * @param pSettingsStack The settings stack (pointer to the top element).2441 */2442 static 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 else2465 {2466 RTMsgError("RTPathFilename: NULL\n");2467 rc = VERR_IS_A_DIRECTORY;2468 }2469 }2470 else2471 rc = scmProcessDirTree(szBuf, pSettingsStack);2472 2473 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);2474 Assert(pPopped == pSettings);2475 scmSettingsDestroy(pSettings);2476 }2477 else2478 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);2479 }2480 else2481 RTMsgError("RTPathAbs: %Rrc\n", rc);2482 return rc;2483 }2484 2485 int 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 only2504 * 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-") != NULL2560 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL2561 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL2562 );2563 if (fAdvanceTwo)2564 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);2565 else2566 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 else2571 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$";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 -
trunk/src/bldprogs/scmsubversion.cpp
r40530 r40534 1 1 /* $Id$ */ 2 2 /** @file 3 * IPRT Testcase / Tool - Source Code Massager .3 * IPRT Testcase / Tool - Source Code Massager, Subversion Access. 4 4 */ 5 5 … … 15 15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. 16 16 */ 17 18 #define SCM_WITHOUT_LIBSVN 17 19 18 20 /******************************************************************************* … … 35 37 #include <iprt/string.h> 36 38 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. */ 52 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE; 53 54 /** 55 * SVN property. 56 */ 57 typedef 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. */ 67 typedef SCMSVNPROP *PSCMSVNPROP; 68 /** Pointer to a const SVN property. */ 69 typedef SCMSVNPROP const *PCSCMSVNPROP; 70 71 72 /** 73 * Rewriter state. 74 */ 75 typedef 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. */ 88 typedef 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 */ 101 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 102 103 104 /** 105 * Configuration entry. 106 */ 107 typedef 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; 116 typedef SCMCFGENTRY *PSCMCFGENTRY; 117 typedef SCMCFGENTRY const *PCSCMCFGENTRY; 118 119 120 /** 121 * Source Code Massager Settings. 122 */ 123 typedef 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. */ 155 typedef 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 */ 165 typedef 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 */ 212 typedef struct SCMPATRNOPTPAIR 213 { 214 char *pszPattern; 215 char *pszOptions; 216 } SCMPATRNOPTPAIR; 217 /** Pointer to a pattern + option pair. */ 218 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR; 219 220 221 /** Pointer to a settings set. */ 222 typedef 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 */ 240 typedef 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. */ 254 typedef SCMSETTINGS const *PCSCMSETTINGS; 255 256 257 /******************************************************************************* 258 * Internal Functions * 259 *******************************************************************************/ 260 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 261 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 262 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 263 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 264 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 265 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 266 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 267 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 268 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 269 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 270 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings); 271 272 273 /******************************************************************************* 274 * Global Variables * 275 *******************************************************************************/ 276 static const char g_szProgName[] = "scm"; 277 static const char *g_pszChangedSuff = ""; 278 static const char g_szTabSpaces[16+1] = " "; 279 static bool g_fDryRun = true; 280 static bool g_fDiffSpecialChars = true; 281 static bool g_fDiffIgnoreEol = false; 282 static bool g_fDiffIgnoreLeadingWS = false; 283 static bool g_fDiffIgnoreTrailingWS = false; 284 static int g_iVerbosity = 2;//99; //0; 285 286 /** The global settings. */ 287 static 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. */ 307 static 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). */ 338 static const char *g_pszFileFilter = NULL; 339 340 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] = 341 { 342 rewrite_SvnNoExecutable, 343 rewrite_Makefile_kup 344 }; 345 346 static 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 356 static 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 367 static 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 377 static 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 387 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] = 388 { 389 rewrite_ForceLF, 390 rewrite_ExpandTabs, 391 rewrite_StripTrailingBlanks 392 }; 393 394 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] = 395 { 396 rewrite_ForceCRLF, 397 rewrite_ExpandTabs, 398 rewrite_StripTrailingBlanks 399 }; 400 401 static 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 */ 424 static 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 */ 455 static 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 */ 465 static 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 */ 495 static 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 */ 638 static 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 */ 672 static 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 */ 688 static 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 */ 708 static 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 */ 721 static 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 */ 745 static 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 */ 771 static 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 */ 835 static 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 */ 890 static 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 */ 924 static 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 */ 978 static 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 */ 999 static 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 */ 1031 static 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 */ 1052 static 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 */ 1068 static 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 */ 1121 static 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 39 #include "scm.h" 40 41 1148 42 1149 43 #ifdef SCM_WITHOUT_LIBSVN … … 1268 162 * @param pState The rewrite state to work on. 1269 163 */ 1270 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)164 bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState) 1271 165 { 1272 166 #ifdef SCM_WITHOUT_LIBSVN … … 1298 192 * using RTStrFree. Optional. 1299 193 */ 1300 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)194 int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue) 1301 195 { 1302 196 /* … … 1456 350 * @param pszValue The value. NULL means deleting it. 1457 351 */ 1458 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)352 int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue) 1459 353 { 1460 354 /* … … 1517 411 * @param pszName The name of the property to delete. 1518 412 */ 1519 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)1520 { 1521 return scmSvnSetProperty(pState, pszName, NULL);413 int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName) 414 { 415 return ScmSvnSetProperty(pState, pszName, NULL); 1522 416 } 1523 417 … … 1530 424 * should be applied. 1531 425 */ 1532 static int scmSvnDisplayChanges(PSCMRWSTATE pState)426 int ScmSvnDisplayChanges(PSCMRWSTATE pState) 1533 427 { 1534 428 size_t i = pState->cSvnPropChanges; … … 1553 447 * should be applied. 1554 448 */ 1555 static int scmSvnApplyChanges(PSCMRWSTATE pState)449 int ScmSvnApplyChanges(PSCMRWSTATE pState) 1556 450 { 1557 451 #ifdef SCM_WITHOUT_LIBSVN … … 1566 460 * Iterate thru the changes and apply them by starting the svn client. 1567 461 */ 1568 for (size_t i = 0; i < pState->cSvnPropChanges; i++)462 for (size_t i = 0; i < pState->cSvnPropChanges; i++) 1569 463 { 1570 464 const char *apszArgv[6]; … … 1615 509 1616 510 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 */ 1628 static 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 */ 1667 static 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 */ 1727 static 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 */ 1785 static 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 */ 1802 static 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 */ 1815 static 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 */ 1831 static 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 */ 1899 static 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 */ 1925 static 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 */ 1975 static 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 */ 1998 static 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 */ 2034 static 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 */ 2054 static 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 */ 2231 static 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 */ 2266 static 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 */ 2294 static 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 */ 2418 static 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 */ 2442 static 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 2485 int 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$"; 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 511
Note:
See TracChangeset
for help on using the changeset viewer.