VirtualBox

source: vbox/trunk/src/bldprogs/scmsubversion.cpp@ 41766

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

scm: more splitting and some header merging.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.3 KB
Line 
1/* $Id: scmsubversion.cpp 40534 2012-03-19 11:49:34Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
4 */
5
6/*
7 * Copyright (C) 2010-2012 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define SCM_WITHOUT_LIBSVN
19
20/*******************************************************************************
21* Header Files *
22*******************************************************************************/
23#include <iprt/assert.h>
24#include <iprt/ctype.h>
25#include <iprt/dir.h>
26#include <iprt/env.h>
27#include <iprt/file.h>
28#include <iprt/err.h>
29#include <iprt/getopt.h>
30#include <iprt/initterm.h>
31#include <iprt/mem.h>
32#include <iprt/message.h>
33#include <iprt/param.h>
34#include <iprt/path.h>
35#include <iprt/process.h>
36#include <iprt/stream.h>
37#include <iprt/string.h>
38
39#include "scm.h"
40
41
42
43#ifdef SCM_WITHOUT_LIBSVN
44
45/**
46 * Callback that is call for each path to search.
47 */
48static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
49{
50 char *pszDst = (char *)pvUser1;
51 size_t cchDst = (size_t)pvUser2;
52 if (cchDst > cchPath)
53 {
54 memcpy(pszDst, pchPath, cchPath);
55 pszDst[cchPath] = '\0';
56#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
57 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
58#else
59 int rc = RTPathAppend(pszDst, cchDst, "svn");
60#endif
61 if ( RT_SUCCESS(rc)
62 && RTFileExists(pszDst))
63 return VINF_SUCCESS;
64 }
65 return VERR_TRY_AGAIN;
66}
67
68
69/**
70 * Finds the svn binary.
71 *
72 * @param pszPath Where to store it. Worst case, we'll return
73 * "svn" here.
74 * @param cchPath The size of the buffer pointed to by @a pszPath.
75 */
76static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
77{
78 /** @todo code page fun... */
79 Assert(cchPath >= sizeof("svn"));
80#ifdef RT_OS_WINDOWS
81 const char *pszEnvVar = RTEnvGet("Path");
82#else
83 const char *pszEnvVar = RTEnvGet("PATH");
84#endif
85 if (pszPath)
86 {
87#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
88 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
89#else
90 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
91#endif
92 if (RT_SUCCESS(rc))
93 return;
94 }
95 strcpy(pszPath, "svn");
96}
97
98
99/**
100 * Construct a dot svn filename for the file being rewritten.
101 *
102 * @returns IPRT status code.
103 * @param pState The rewrite state (for the name).
104 * @param pszDir The directory, including ".svn/".
105 * @param pszSuff The filename suffix.
106 * @param pszDst The output buffer. RTPATH_MAX in size.
107 */
108static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
109{
110 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
111 RTPathStripFilename(pszDst);
112
113 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
114 if (RT_SUCCESS(rc))
115 {
116 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
117 if (RT_SUCCESS(rc))
118 {
119 size_t cchDst = strlen(pszDst);
120 size_t cchSuff = strlen(pszSuff);
121 if (cchDst + cchSuff < RTPATH_MAX)
122 {
123 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
124 return VINF_SUCCESS;
125 }
126 else
127 rc = VERR_BUFFER_OVERFLOW;
128 }
129 }
130 return rc;
131}
132
133/**
134 * Interprets the specified string as decimal numbers.
135 *
136 * @returns true if parsed successfully, false if not.
137 * @param pch The string (not terminated).
138 * @param cch The string length.
139 * @param pu Where to return the value.
140 */
141static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
142{
143 size_t u = 0;
144 while (cch-- > 0)
145 {
146 char ch = *pch++;
147 if (ch < '0' || ch > '9')
148 return false;
149 u *= 10;
150 u += ch - '0';
151 }
152 *pu = u;
153 return true;
154}
155
156#endif /* SCM_WITHOUT_LIBSVN */
157
158/**
159 * Checks if the file we're operating on is part of a SVN working copy.
160 *
161 * @returns true if it is, false if it isn't or we cannot tell.
162 * @param pState The rewrite state to work on.
163 */
164bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
165{
166#ifdef SCM_WITHOUT_LIBSVN
167 /*
168 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
169 */
170 char szPath[RTPATH_MAX];
171 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
172 if (RT_SUCCESS(rc))
173 return RTFileExists(szPath);
174
175#else
176 NOREF(pState);
177#endif
178 return false;
179}
180
181/**
182 * Queries the value of an SVN property.
183 *
184 * This will automatically adjust for scheduled changes.
185 *
186 * @returns IPRT status code.
187 * @retval VERR_INVALID_STATE if not a SVN WC file.
188 * @retval VERR_NOT_FOUND if the property wasn't found.
189 * @param pState The rewrite state to work on.
190 * @param pszName The property name.
191 * @param ppszValue Where to return the property value. Free this
192 * using RTStrFree. Optional.
193 */
194int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
195{
196 /*
197 * Look it up in the scheduled changes.
198 */
199 uint32_t i = pState->cSvnPropChanges;
200 while (i-- > 0)
201 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
202 {
203 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
204 if (!pszValue)
205 return VERR_NOT_FOUND;
206 if (ppszValue)
207 return RTStrDupEx(ppszValue, pszValue);
208 return VINF_SUCCESS;
209 }
210
211#ifdef SCM_WITHOUT_LIBSVN
212 /*
213 * Hack: Read the .svn/props/<file>.svn-work file exists.
214 */
215 char szPath[RTPATH_MAX];
216 int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
217 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
218 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
219 if (RT_SUCCESS(rc))
220 {
221 SCMSTREAM Stream;
222 rc = ScmStreamInitForReading(&Stream, szPath);
223 if (RT_SUCCESS(rc))
224 {
225 /*
226 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
227 */
228 rc = VERR_NOT_FOUND;
229 size_t const cchName = strlen(pszName);
230 SCMEOL enmEol;
231 size_t cchLine;
232 const char *pchLine;
233 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
234 {
235 /*
236 * Parse the 'K num' / 'END' line.
237 */
238 if ( cchLine == 3
239 && !memcmp(pchLine, "END", 3))
240 break;
241 size_t cchKey;
242 if ( cchLine < 3
243 || pchLine[0] != 'K'
244 || pchLine[1] != ' '
245 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
246 || cchKey == 0
247 || cchKey > 4096)
248 {
249 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
250 rc = VERR_PARSE_ERROR;
251 break;
252 }
253
254 /*
255 * Match the key and skip to the value line. Don't bother with
256 * names containing EOL markers.
257 */
258 size_t const offKey = ScmStreamTell(&Stream);
259 bool fMatch = cchName == cchKey;
260 if (fMatch)
261 {
262 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
263 if (!pchLine)
264 break;
265 fMatch = cchLine == cchName
266 && !memcmp(pchLine, pszName, cchName);
267 }
268
269 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
270 break;
271 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
272 break;
273
274 /*
275 * Read and Parse the 'V num' line.
276 */
277 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
278 if (!pchLine)
279 break;
280 size_t cchValue;
281 if ( cchLine < 3
282 || pchLine[0] != 'V'
283 || pchLine[1] != ' '
284 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
285 || cchValue > _1M)
286 {
287 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
288 rc = VERR_PARSE_ERROR;
289 break;
290 }
291
292 /*
293 * If we have a match, allocate a return buffer and read the
294 * value into it. Otherwise skip this value and continue
295 * searching.
296 */
297 if (fMatch)
298 {
299 if (!ppszValue)
300 rc = VINF_SUCCESS;
301 else
302 {
303 char *pszValue;
304 rc = RTStrAllocEx(&pszValue, cchValue + 1);
305 if (RT_SUCCESS(rc))
306 {
307 rc = ScmStreamRead(&Stream, pszValue, cchValue);
308 if (RT_SUCCESS(rc))
309 *ppszValue = pszValue;
310 else
311 RTStrFree(pszValue);
312 }
313 }
314 break;
315 }
316
317 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
318 break;
319 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
320 break;
321 }
322
323 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
324 {
325 rc = ScmStreamGetStatus(&Stream);
326 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
327 }
328 ScmStreamDelete(&Stream);
329 }
330 }
331
332 if (rc == VERR_FILE_NOT_FOUND)
333 rc = VERR_NOT_FOUND;
334 return rc;
335
336#else
337 NOREF(pState);
338#endif
339 return VERR_NOT_FOUND;
340}
341
342
343/**
344 * Schedules the setting of a property.
345 *
346 * @returns IPRT status code.
347 * @retval VERR_INVALID_STATE if not a SVN WC file.
348 * @param pState The rewrite state to work on.
349 * @param pszName The name of the property to set.
350 * @param pszValue The value. NULL means deleting it.
351 */
352int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
353{
354 /*
355 * Update any existing entry first.
356 */
357 size_t i = pState->cSvnPropChanges;
358 while (i-- > 0)
359 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
360 {
361 if (!pszValue)
362 {
363 RTStrFree(pState->paSvnPropChanges[i].pszValue);
364 pState->paSvnPropChanges[i].pszValue = NULL;
365 }
366 else
367 {
368 char *pszCopy;
369 int rc = RTStrDupEx(&pszCopy, pszValue);
370 if (RT_FAILURE(rc))
371 return rc;
372 pState->paSvnPropChanges[i].pszValue = pszCopy;
373 }
374 return VINF_SUCCESS;
375 }
376
377 /*
378 * Insert a new entry.
379 */
380 i = pState->cSvnPropChanges;
381 if ((i % 32) == 0)
382 {
383 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
384 if (!pvNew)
385 return VERR_NO_MEMORY;
386 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
387 }
388
389 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
390 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
391 if ( pState->paSvnPropChanges[i].pszName
392 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
393 pState->cSvnPropChanges = i + 1;
394 else
395 {
396 RTStrFree(pState->paSvnPropChanges[i].pszName);
397 pState->paSvnPropChanges[i].pszName = NULL;
398 RTStrFree(pState->paSvnPropChanges[i].pszValue);
399 pState->paSvnPropChanges[i].pszValue = NULL;
400 return VERR_NO_MEMORY;
401 }
402 return VINF_SUCCESS;
403}
404
405
406/**
407 * Schedules a property deletion.
408 *
409 * @returns IPRT status code.
410 * @param pState The rewrite state to work on.
411 * @param pszName The name of the property to delete.
412 */
413int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
414{
415 return ScmSvnSetProperty(pState, pszName, NULL);
416}
417
418
419/**
420 * Applies any SVN property changes to the work copy of the file.
421 *
422 * @returns IPRT status code.
423 * @param pState The rewrite state which SVN property changes
424 * should be applied.
425 */
426int ScmSvnDisplayChanges(PSCMRWSTATE pState)
427{
428 size_t i = pState->cSvnPropChanges;
429 while (i-- > 0)
430 {
431 const char *pszName = pState->paSvnPropChanges[i].pszName;
432 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
433 if (pszValue)
434 ScmVerbose(pState, 0, "svn ps '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
435 else
436 ScmVerbose(pState, 0, "svn pd '%s' %s\n", pszName, pszValue, pState->pszFilename);
437 }
438
439 return VINF_SUCCESS;
440}
441
442/**
443 * Applies any SVN property changes to the work copy of the file.
444 *
445 * @returns IPRT status code.
446 * @param pState The rewrite state which SVN property changes
447 * should be applied.
448 */
449int ScmSvnApplyChanges(PSCMRWSTATE pState)
450{
451#ifdef SCM_WITHOUT_LIBSVN
452 /*
453 * This sucks. We gotta find svn(.exe).
454 */
455 static char s_szSvnPath[RTPATH_MAX];
456 if (s_szSvnPath[0] == '\0')
457 scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
458
459 /*
460 * Iterate thru the changes and apply them by starting the svn client.
461 */
462 for (size_t i = 0; i < pState->cSvnPropChanges; i++)
463 {
464 const char *apszArgv[6];
465 apszArgv[0] = s_szSvnPath;
466 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
467 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
468 int iArg = 3;
469 if (pState->paSvnPropChanges[i].pszValue)
470 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
471 apszArgv[iArg++] = pState->pszFilename;
472 apszArgv[iArg++] = NULL;
473 ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
474 apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
475
476 RTPROCESS pid;
477 int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
478 if (RT_SUCCESS(rc))
479 {
480 RTPROCSTATUS Status;
481 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
482 if ( RT_SUCCESS(rc)
483 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
484 || Status.iStatus != 0) )
485 {
486 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
487 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
488 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
489 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
490 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
491 : "abducted by alien",
492 Status.iStatus);
493 return VERR_GENERAL_FAILURE;
494 }
495 }
496 if (RT_FAILURE(rc))
497 {
498 RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
499 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
500 return rc;
501 }
502 }
503
504 return VINF_SUCCESS;
505#else
506 return VERR_NOT_IMPLEMENTED;
507#endif
508}
509
510
511
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette