VirtualBox

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

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

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

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.3 KB
Line 
1/* $Id: scmsubversion.cpp 41966 2012-06-29 02:53:56Z 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* Global Variables *
44*******************************************************************************/
45static char g_szSvnPath[RTPATH_MAX];
46static enum
47{
48 kScmSvnVersion_Ancient = 1,
49 kScmSvnVersion_1_6,
50 kScmSvnVersion_1_7,
51 kScmSvnVersion_End
52} g_enmSvnVersion = kScmSvnVersion_Ancient;
53
54
55#ifdef SCM_WITHOUT_LIBSVN
56
57/**
58 * Callback that is call for each path to search.
59 */
60static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
61{
62 char *pszDst = (char *)pvUser1;
63 size_t cchDst = (size_t)pvUser2;
64 if (cchDst > cchPath)
65 {
66 memcpy(pszDst, pchPath, cchPath);
67 pszDst[cchPath] = '\0';
68#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
69 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
70#else
71 int rc = RTPathAppend(pszDst, cchDst, "svn");
72#endif
73 if ( RT_SUCCESS(rc)
74 && RTFileExists(pszDst))
75 return VINF_SUCCESS;
76 }
77 return VERR_TRY_AGAIN;
78}
79
80#include <iprt/handle.h>
81#include <iprt/pipe.h>
82#include <iprt/poll.h>
83
84/**
85 * Reads from a pipe.
86 *
87 * @returns @a rc or other status code.
88 * @param rc The current status of the operation. Error status
89 * are preserved and returned.
90 * @param phPipeR Pointer to the pipe handle.
91 * @param pcbAllocated Pointer to the buffer size variable.
92 * @param poffCur Pointer to the buffer offset variable.
93 * @param ppszBuffer Pointer to the buffer pointer variable.
94 */
95static int rtProcProcessOutput(int rc, PRTPIPE phPipeR, size_t *pcbAllocated, size_t *poffCur, char **ppszBuffer,
96 RTPOLLSET hPollSet, uint32_t idPollSet)
97{
98 size_t cbRead;
99 char szTmp[_4K - 1];
100 for (;;)
101 {
102 int rc2 = RTPipeRead(*phPipeR, szTmp, sizeof(szTmp), &cbRead);
103 if (RT_SUCCESS(rc2) && cbRead)
104 {
105 /* Resize the buffer. */
106 if (*poffCur + cbRead >= *pcbAllocated)
107 {
108 if (*pcbAllocated >= _1G)
109 {
110 RTPollSetRemove(hPollSet, idPollSet);
111 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
112 *phPipeR = NIL_RTPIPE;
113 return RT_SUCCESS(rc) ? VERR_TOO_MUCH_DATA : rc;
114 }
115
116 size_t cbNew = *pcbAllocated ? *pcbAllocated * 2 : sizeof(szTmp) + 1;
117 Assert(*poffCur + cbRead < cbNew);
118 rc2 = RTStrRealloc(ppszBuffer, cbNew);
119 if (RT_FAILURE(rc2))
120 {
121 RTPollSetRemove(hPollSet, idPollSet);
122 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
123 *phPipeR = NIL_RTPIPE;
124 return RT_SUCCESS(rc) ? rc2 : rc;
125 }
126 *pcbAllocated = cbNew;
127 }
128
129 /* Append the new data, terminating it. */
130 memcpy(*ppszBuffer + *poffCur, szTmp, cbRead);
131 *poffCur += cbRead;
132 (*ppszBuffer)[*poffCur] = '\0';
133
134 /* Check for null terminators in the string. */
135 if (RT_SUCCESS(rc) && memchr(szTmp, '\0', cbRead))
136 rc = VERR_NO_TRANSLATION;
137
138 /* If we read a full buffer, try read some more. */
139 if (RT_SUCCESS(rc) && cbRead == sizeof(szTmp))
140 continue;
141 }
142 else if (rc2 != VINF_TRY_AGAIN)
143 {
144 if (RT_FAILURE(rc) && rc2 != VERR_BROKEN_PIPE)
145 rc = rc2;
146 RTPollSetRemove(hPollSet, idPollSet);
147 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
148 *phPipeR = NIL_RTPIPE;
149 }
150 return rc;
151 }
152}
153
154/** @name RTPROCEXEC_FLAGS_XXX - flags for RTProcExec and RTProcExecToString.
155 * @{ */
156/** Redirect /dev/null to standard input. */
157#define RTPROCEXEC_FLAGS_STDIN_NULL RT_BIT_32(0)
158/** Redirect standard output to /dev/null. */
159#define RTPROCEXEC_FLAGS_STDOUT_NULL RT_BIT_32(1)
160/** Redirect standard error to /dev/null. */
161#define RTPROCEXEC_FLAGS_STDERR_NULL RT_BIT_32(2)
162/** Redirect all standard output to /dev/null as well as directing /dev/null
163 * to standard input. */
164#define RTPROCEXEC_FLAGS_STD_NULL ( RTPROCEXEC_FLAGS_STDIN_NULL \
165 | RTPROCEXEC_FLAGS_STDOUT_NULL \
166 | RTPROCEXEC_FLAGS_STDERR_NULL)
167/** Mask containing the valid flags. */
168#define RTPROCEXEC_FLAGS_VALID_MASK UINT32_C(0x00000007)
169/** @} */
170
171/**
172 * Runs a process, collecting the standard output and/or standard error.
173 *
174 *
175 * @returns IPRT status code
176 * @retval VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8
177 * or contains a nul character.
178 * @retval VERR_TOO_MUCH_DATA if the process produced too much data.
179 *
180 * @param pszExec Executable image to use to create the child process.
181 * @param papszArgs Pointer to an array of arguments to the child. The
182 * array terminated by an entry containing NULL.
183 * @param hEnv Handle to the environment block for the child.
184 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. The @a
185 * ppszStdOut and @a ppszStdErr parameters takes precedence
186 * over redirection flags.
187 * @param pStatus Where to return the status on success.
188 * @param ppszStdOut Where to return the text written to standard output. If
189 * NULL then standard output will not be collected and go
190 * to the standard output handle of the process.
191 * Free with RTStrFree, regardless of return status.
192 * @param ppszStdErr Where to return the text written to standard error. If
193 * NULL then standard output will not be collected and go
194 * to the standard error handle of the process.
195 * Free with RTStrFree, regardless of return status.
196 */
197int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
198 PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr)
199{
200 int rc2;
201
202 /*
203 * Clear output arguments (no returning failure here, simply crash!).
204 */
205 AssertPtr(pStatus);
206 pStatus->enmReason = RTPROCEXITREASON_ABEND;
207 pStatus->iStatus = RTEXITCODE_FAILURE;
208 AssertPtrNull(ppszStdOut);
209 if (ppszStdOut)
210 *ppszStdOut = NULL;
211 AssertPtrNull(ppszStdOut);
212 if (ppszStdErr)
213 *ppszStdErr = NULL;
214
215 /*
216 * Check input arguments.
217 */
218 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
219
220 /*
221 * Do we need a standard input bitbucket?
222 */
223 int rc = VINF_SUCCESS;
224 PRTHANDLE phChildStdIn = NULL;
225 RTHANDLE hChildStdIn;
226 hChildStdIn.enmType = RTHANDLETYPE_FILE;
227 hChildStdIn.u.hFile = NIL_RTFILE;
228 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
229 {
230 phChildStdIn = &hChildStdIn;
231 rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ);
232 }
233
234 /*
235 * Create the output pipes / bitbuckets.
236 */
237 RTPIPE hPipeStdOutR = NIL_RTPIPE;
238 PRTHANDLE phChildStdOut = NULL;
239 RTHANDLE hChildStdOut;
240 hChildStdOut.enmType = RTHANDLETYPE_PIPE;
241 hChildStdOut.u.hPipe = NIL_RTPIPE;
242 if (ppszStdOut && RT_SUCCESS(rc))
243 {
244 phChildStdOut = &hChildStdOut;
245 rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/);
246 }
247 else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
248 {
249 phChildStdOut = &hChildStdOut;
250 hChildStdOut.enmType = RTHANDLETYPE_FILE;
251 hChildStdOut.u.hFile = NIL_RTFILE;
252 rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE);
253 }
254
255 RTPIPE hPipeStdErrR = NIL_RTPIPE;
256 PRTHANDLE phChildStdErr = NULL;
257 RTHANDLE hChildStdErr;
258 hChildStdErr.enmType = RTHANDLETYPE_PIPE;
259 hChildStdErr.u.hPipe = NIL_RTPIPE;
260 if (ppszStdErr && RT_SUCCESS(rc))
261 {
262 phChildStdErr = &hChildStdErr;
263 rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/);
264 }
265 else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
266 {
267 phChildStdErr = &hChildStdErr;
268 hChildStdErr.enmType = RTHANDLETYPE_FILE;
269 hChildStdErr.u.hFile = NIL_RTFILE;
270 rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE);
271 }
272
273 if (RT_SUCCESS(rc))
274 {
275 RTPOLLSET hPollSet;
276 rc = RTPollSetCreate(&hPollSet);
277 if (RT_SUCCESS(rc))
278 {
279 if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc))
280 rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1);
281 if (hPipeStdErrR != NIL_RTPIPE)
282 rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2);
283 }
284 if (RT_SUCCESS(rc))
285 {
286 /*
287 * Create the process.
288 */
289 RTPROCESS hProc;
290 rc = RTProcCreateEx(g_szSvnPath,
291 papszArgs,
292 RTENV_DEFAULT,
293 0 /*fFlags*/,
294 NULL /*phStdIn*/,
295 phChildStdOut,
296 phChildStdErr,
297 NULL /*pszAsUser*/,
298 NULL /*pszPassword*/,
299 &hProc);
300 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
301 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
302
303 if (RT_SUCCESS(rc))
304 {
305 /*
306 * Process output and wait for the process to finish.
307 */
308 size_t cbStdOut = 0;
309 size_t offStdOut = 0;
310 size_t cbStdErr = 0;
311 size_t offStdErr = 0;
312 for (;;)
313 {
314 if (hPipeStdOutR != NIL_RTPIPE)
315 rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
316 if (hPipeStdErrR != NIL_RTPIPE)
317 rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
318 if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
319 break;
320
321 if (hProc != NIL_RTPROCESS)
322 {
323 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
324 if (rc2 != VERR_PROCESS_RUNNING)
325 {
326 if (RT_FAILURE(rc2))
327 rc = rc2;
328 hProc = NIL_RTPROCESS;
329 }
330 }
331
332 rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
333 Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
334 }
335
336 if (RT_SUCCESS(rc))
337 {
338 if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
339 || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
340 rc = VERR_NO_TRANSLATION;
341 }
342
343 /*
344 * No more output, just wait for it to finish.
345 */
346 if (hProc != NIL_RTPROCESS)
347 {
348 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
349 if (RT_FAILURE(rc2))
350 rc = rc2;
351 }
352 }
353 RTPollSetDestroy(hPollSet);
354 }
355 }
356
357 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
358 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
359 rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2);
360 rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2);
361 rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2);
362 return rc;
363}
364
365
366/**
367 * Runs a process, waiting for it to complete.
368 *
369 * @returns IPRT status code
370 *
371 * @param pszExec Executable image to use to create the child process.
372 * @param papszArgs Pointer to an array of arguments to the child. The
373 * array terminated by an entry containing NULL.
374 * @param hEnv Handle to the environment block for the child.
375 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX.
376 * @param pStatus Where to return the status on success.
377 */
378int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
379 PRTPROCSTATUS pStatus)
380{
381 int rc;
382
383 /*
384 * Clear output argument (no returning failure here, simply crash!).
385 */
386 AssertPtr(pStatus);
387 pStatus->enmReason = RTPROCEXITREASON_ABEND;
388 pStatus->iStatus = RTEXITCODE_FAILURE;
389
390 /*
391 * Check input arguments.
392 */
393 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
394
395 /*
396 * Set up /dev/null redirections.
397 */
398 PRTHANDLE aph[3] = { NULL, NULL, NULL };
399 RTHANDLE ah[3];
400 for (uint32_t i = 0; i < 3; i++)
401 {
402 ah[i].enmType = RTHANDLETYPE_FILE;
403 ah[i].u.hFile = NIL_RTFILE;
404 }
405 rc = VINF_SUCCESS;
406 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
407 {
408 aph[0] = &ah[0];
409 rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
410 }
411 if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
412 {
413 aph[1] = &ah[1];
414 rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
415 }
416 if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
417 {
418 aph[2] = &ah[2];
419 rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
420 }
421
422 /*
423 * Create the process.
424 */
425 RTPROCESS hProc;
426 if (RT_SUCCESS(rc))
427 rc = RTProcCreateEx(g_szSvnPath,
428 papszArgs,
429 RTENV_DEFAULT,
430 0 /*fFlags*/,
431 aph[0],
432 aph[1],
433 aph[2],
434 NULL /*pszAsUser*/,
435 NULL /*pszPassword*/,
436 &hProc);
437
438 for (uint32_t i = 0; i < 3; i++)
439 RTFileClose(ah[i].u.hFile);
440
441 if (RT_SUCCESS(rc))
442 rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
443 return rc;
444}
445
446
447
448/**
449 * Executes SVN and gets the output.
450 *
451 * Standard error is suppressed.
452 *
453 * @returns VINF_SUCCESS if the command executed successfully.
454 * @param pState The rewrite state to work on. Can be NULL.
455 * @param papszArgs The SVN argument.
456 * @param fNormalFailureOk Whether normal failure is ok.
457 * @param ppszStdOut Where to return the output on success.
458 */
459static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut)
460{
461 *ppszStdOut = NULL;
462
463 char *pszCmdLine = NULL;
464 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
465 if (RT_FAILURE(rc))
466 return rc;
467 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
468
469 RTPROCSTATUS Status;
470 rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT,
471 RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL);
472
473 if ( RT_SUCCESS(rc)
474 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
475 || Status.iStatus != 0) )
476 {
477 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
478 RTMsgError("%s: %s -> %s %u\n",
479 pszCmdLine,
480 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
481 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
482 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
483 : "abducted by alien",
484 Status.iStatus);
485 rc = VERR_GENERAL_FAILURE;
486 }
487 else if (RT_FAILURE(rc))
488 {
489 if (pState)
490 RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc);
491 else
492 RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc);
493 }
494
495 if (RT_FAILURE(rc))
496 {
497 RTStrFree(*ppszStdOut);
498 *ppszStdOut = NULL;
499 }
500 RTStrFree(pszCmdLine);
501 return rc;
502}
503
504
505/**
506 * Executes SVN.
507 *
508 * Standard error and standard output is suppressed.
509 *
510 * @returns VINF_SUCCESS if the command executed successfully.
511 * @param pState The rewrite state to work on.
512 * @param papszArgs The SVN argument.
513 * @param fNormalFailureOk Whether normal failure is ok.
514 */
515static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk)
516{
517 char *pszCmdLine = NULL;
518 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
519 if (RT_FAILURE(rc))
520 return rc;
521 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
522
523 /* Lazy bird uses RTProcExecToString. */
524 RTPROCSTATUS Status;
525 rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status);
526
527 if ( RT_SUCCESS(rc)
528 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
529 || Status.iStatus != 0) )
530 {
531 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
532 RTMsgError("%s: %s -> %s %u\n",
533 pState->pszFilename,
534 pszCmdLine,
535 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
536 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
537 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
538 : "abducted by alien",
539 Status.iStatus);
540 rc = VERR_GENERAL_FAILURE;
541 }
542 else if (RT_FAILURE(rc))
543 RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc);
544
545 RTStrFree(pszCmdLine);
546 return rc;
547}
548
549
550/**
551 * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion.
552 */
553static void scmSvnFindSvnBinary(PSCMRWSTATE pState)
554{
555 /* Already been called? */
556 if (g_szSvnPath[0] != '\0')
557 return;
558
559 /*
560 * Locate it.
561 */
562 /** @todo code page fun... */
563#ifdef RT_OS_WINDOWS
564 const char *pszEnvVar = RTEnvGet("Path");
565#else
566 const char *pszEnvVar = RTEnvGet("PATH");
567#endif
568 if (pszEnvVar)
569 {
570#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
571 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
572#else
573 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
574#endif
575 if (RT_FAILURE(rc))
576 strcpy(g_szSvnPath, "svn");
577 }
578 else
579 strcpy(g_szSvnPath, "svn");
580
581 /*
582 * Check the version.
583 */
584 const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL };
585 char *pszVersion;
586 int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion);
587 if (RT_SUCCESS(rc))
588 {
589 char *pszStripped = RTStrStrip(pszVersion);
590 if (RTStrVersionCompare(pszVersion, "1.7") >= 0)
591 g_enmSvnVersion = kScmSvnVersion_1_7;
592 else if (RTStrVersionCompare(pszVersion, "1.6") >= 0)
593 g_enmSvnVersion = kScmSvnVersion_1_6;
594 else
595 g_enmSvnVersion = kScmSvnVersion_Ancient;
596 RTStrFree(pszVersion);
597 }
598 else
599 g_enmSvnVersion = kScmSvnVersion_Ancient;
600}
601
602
603/**
604 * Construct a dot svn filename for the file being rewritten.
605 *
606 * @returns IPRT status code.
607 * @param pState The rewrite state (for the name).
608 * @param pszDir The directory, including ".svn/".
609 * @param pszSuff The filename suffix.
610 * @param pszDst The output buffer. RTPATH_MAX in size.
611 */
612static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
613{
614 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
615 RTPathStripFilename(pszDst);
616
617 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
618 if (RT_SUCCESS(rc))
619 {
620 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
621 if (RT_SUCCESS(rc))
622 {
623 size_t cchDst = strlen(pszDst);
624 size_t cchSuff = strlen(pszSuff);
625 if (cchDst + cchSuff < RTPATH_MAX)
626 {
627 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
628 return VINF_SUCCESS;
629 }
630 else
631 rc = VERR_BUFFER_OVERFLOW;
632 }
633 }
634 return rc;
635}
636
637/**
638 * Interprets the specified string as decimal numbers.
639 *
640 * @returns true if parsed successfully, false if not.
641 * @param pch The string (not terminated).
642 * @param cch The string length.
643 * @param pu Where to return the value.
644 */
645static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
646{
647 size_t u = 0;
648 while (cch-- > 0)
649 {
650 char ch = *pch++;
651 if (ch < '0' || ch > '9')
652 return false;
653 u *= 10;
654 u += ch - '0';
655 }
656 *pu = u;
657 return true;
658}
659
660#endif /* SCM_WITHOUT_LIBSVN */
661
662/**
663 * Checks if the file we're operating on is part of a SVN working copy.
664 *
665 * @returns true if it is, false if it isn't or we cannot tell.
666 * @param pState The rewrite state to work on.
667 */
668bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
669{
670#ifdef SCM_WITHOUT_LIBSVN
671 scmSvnFindSvnBinary(pState);
672 if (g_enmSvnVersion < kScmSvnVersion_1_7)
673 {
674 /*
675 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
676 */
677 char szPath[RTPATH_MAX];
678 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
679 if (RT_SUCCESS(rc))
680 return RTFileExists(szPath);
681 }
682 else
683 {
684 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pState->pszFilename, NULL };
685 char *pszValue;
686 int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
687 if (RT_SUCCESS(rc))
688 {
689 RTStrFree(pszValue);
690 return true;
691 }
692 }
693
694#else
695 NOREF(pState);
696#endif
697 return false;
698}
699
700/**
701 * Checks if the specified directory is part of a SVN working copy.
702 *
703 * @returns true if it is, false if it isn't or we cannot tell.
704 * @param pszDir The directory in question.
705 */
706bool ScmSvnIsDirInWorkingCopy(const char *pszDir)
707{
708#ifdef SCM_WITHOUT_LIBSVN
709 scmSvnFindSvnBinary(NULL);
710 if (g_enmSvnVersion < kScmSvnVersion_1_7)
711 {
712 /*
713 * Hack: check if the .svn/ dir exists.
714 */
715 char szPath[RTPATH_MAX];
716 int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn");
717 if (RT_SUCCESS(rc))
718 return RTDirExists(szPath);
719 }
720 else
721 {
722 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL };
723 char *pszValue;
724 int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue);
725 if (RT_SUCCESS(rc))
726 {
727 RTStrFree(pszValue);
728 return true;
729 }
730 }
731
732#else
733 NOREF(pState);
734#endif
735 return false;
736}
737
738/**
739 * Queries the value of an SVN property.
740 *
741 * This will automatically adjust for scheduled changes.
742 *
743 * @returns IPRT status code.
744 * @retval VERR_INVALID_STATE if not a SVN WC file.
745 * @retval VERR_NOT_FOUND if the property wasn't found.
746 * @param pState The rewrite state to work on.
747 * @param pszName The property name.
748 * @param ppszValue Where to return the property value. Free this
749 * using RTStrFree. Optional.
750 */
751int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
752{
753 /*
754 * Look it up in the scheduled changes.
755 */
756 size_t i = pState->cSvnPropChanges;
757 while (i-- > 0)
758 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
759 {
760 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
761 if (!pszValue)
762 return VERR_NOT_FOUND;
763 if (ppszValue)
764 return RTStrDupEx(ppszValue, pszValue);
765 return VINF_SUCCESS;
766 }
767
768#ifdef SCM_WITHOUT_LIBSVN
769 int rc;
770 scmSvnFindSvnBinary(pState);
771 if (g_enmSvnVersion < kScmSvnVersion_1_7)
772 {
773 /*
774 * Hack: Read the .svn/props/<file>.svn-work file exists.
775 */
776 char szPath[RTPATH_MAX];
777 rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
778 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
779 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
780 if (RT_SUCCESS(rc))
781 {
782 SCMSTREAM Stream;
783 rc = ScmStreamInitForReading(&Stream, szPath);
784 if (RT_SUCCESS(rc))
785 {
786 /*
787 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
788 */
789 rc = VERR_NOT_FOUND;
790 size_t const cchName = strlen(pszName);
791 SCMEOL enmEol;
792 size_t cchLine;
793 const char *pchLine;
794 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
795 {
796 /*
797 * Parse the 'K num' / 'END' line.
798 */
799 if ( cchLine == 3
800 && !memcmp(pchLine, "END", 3))
801 break;
802 size_t cchKey;
803 if ( cchLine < 3
804 || pchLine[0] != 'K'
805 || pchLine[1] != ' '
806 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
807 || cchKey == 0
808 || cchKey > 4096)
809 {
810 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
811 rc = VERR_PARSE_ERROR;
812 break;
813 }
814
815 /*
816 * Match the key and skip to the value line. Don't bother with
817 * names containing EOL markers.
818 */
819 size_t const offKey = ScmStreamTell(&Stream);
820 bool fMatch = cchName == cchKey;
821 if (fMatch)
822 {
823 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
824 if (!pchLine)
825 break;
826 fMatch = cchLine == cchName
827 && !memcmp(pchLine, pszName, cchName);
828 }
829
830 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
831 break;
832 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
833 break;
834
835 /*
836 * Read and Parse the 'V num' line.
837 */
838 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
839 if (!pchLine)
840 break;
841 size_t cchValue;
842 if ( cchLine < 3
843 || pchLine[0] != 'V'
844 || pchLine[1] != ' '
845 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
846 || cchValue > _1M)
847 {
848 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
849 rc = VERR_PARSE_ERROR;
850 break;
851 }
852
853 /*
854 * If we have a match, allocate a return buffer and read the
855 * value into it. Otherwise skip this value and continue
856 * searching.
857 */
858 if (fMatch)
859 {
860 if (!ppszValue)
861 rc = VINF_SUCCESS;
862 else
863 {
864 char *pszValue;
865 rc = RTStrAllocEx(&pszValue, cchValue + 1);
866 if (RT_SUCCESS(rc))
867 {
868 rc = ScmStreamRead(&Stream, pszValue, cchValue);
869 if (RT_SUCCESS(rc))
870 *ppszValue = pszValue;
871 else
872 RTStrFree(pszValue);
873 }
874 }
875 break;
876 }
877
878 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
879 break;
880 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
881 break;
882 }
883
884 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
885 {
886 rc = ScmStreamGetStatus(&Stream);
887 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
888 }
889 ScmStreamDelete(&Stream);
890 }
891 }
892
893 if (rc == VERR_FILE_NOT_FOUND)
894 rc = VERR_NOT_FOUND;
895 }
896 else
897 {
898 const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL };
899 char *pszValue;
900 rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue);
901 if (RT_SUCCESS(rc))
902 {
903 if (pszValue && *pszValue)
904 {
905 if (ppszValue)
906 {
907 *ppszValue = pszValue;
908 pszValue = NULL;
909 }
910 }
911 else
912 rc = VERR_NOT_FOUND;
913 RTStrFree(pszValue);
914 }
915 }
916 return rc;
917
918#else
919 NOREF(pState);
920#endif
921 return VERR_NOT_FOUND;
922}
923
924
925/**
926 * Schedules the setting of a property.
927 *
928 * @returns IPRT status code.
929 * @retval VERR_INVALID_STATE if not a SVN WC file.
930 * @param pState The rewrite state to work on.
931 * @param pszName The name of the property to set.
932 * @param pszValue The value. NULL means deleting it.
933 */
934int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
935{
936 /*
937 * Update any existing entry first.
938 */
939 size_t i = pState->cSvnPropChanges;
940 while (i-- > 0)
941 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
942 {
943 if (!pszValue)
944 {
945 RTStrFree(pState->paSvnPropChanges[i].pszValue);
946 pState->paSvnPropChanges[i].pszValue = NULL;
947 }
948 else
949 {
950 char *pszCopy;
951 int rc = RTStrDupEx(&pszCopy, pszValue);
952 if (RT_FAILURE(rc))
953 return rc;
954 pState->paSvnPropChanges[i].pszValue = pszCopy;
955 }
956 return VINF_SUCCESS;
957 }
958
959 /*
960 * Insert a new entry.
961 */
962 i = pState->cSvnPropChanges;
963 if ((i % 32) == 0)
964 {
965 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
966 if (!pvNew)
967 return VERR_NO_MEMORY;
968 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
969 }
970
971 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
972 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
973 if ( pState->paSvnPropChanges[i].pszName
974 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
975 pState->cSvnPropChanges = i + 1;
976 else
977 {
978 RTStrFree(pState->paSvnPropChanges[i].pszName);
979 pState->paSvnPropChanges[i].pszName = NULL;
980 RTStrFree(pState->paSvnPropChanges[i].pszValue);
981 pState->paSvnPropChanges[i].pszValue = NULL;
982 return VERR_NO_MEMORY;
983 }
984 return VINF_SUCCESS;
985}
986
987
988/**
989 * Schedules a property deletion.
990 *
991 * @returns IPRT status code.
992 * @param pState The rewrite state to work on.
993 * @param pszName The name of the property to delete.
994 */
995int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
996{
997 return ScmSvnSetProperty(pState, pszName, NULL);
998}
999
1000
1001/**
1002 * Applies any SVN property changes to the work copy of the file.
1003 *
1004 * @returns IPRT status code.
1005 * @param pState The rewrite state which SVN property changes
1006 * should be applied.
1007 */
1008int ScmSvnDisplayChanges(PSCMRWSTATE pState)
1009{
1010 size_t i = pState->cSvnPropChanges;
1011 while (i-- > 0)
1012 {
1013 const char *pszName = pState->paSvnPropChanges[i].pszName;
1014 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1015 if (pszValue)
1016 ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
1017 else
1018 ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename);
1019 }
1020
1021 return VINF_SUCCESS;
1022}
1023
1024/**
1025 * Applies any SVN property changes to the work copy of the file.
1026 *
1027 * @returns IPRT status code.
1028 * @param pState The rewrite state which SVN property changes
1029 * should be applied.
1030 */
1031int ScmSvnApplyChanges(PSCMRWSTATE pState)
1032{
1033#ifdef SCM_WITHOUT_LIBSVN
1034 scmSvnFindSvnBinary(pState);
1035
1036 /*
1037 * Iterate thru the changes and apply them by starting the svn client.
1038 */
1039 for (size_t i = 0; i < pState->cSvnPropChanges; i++)
1040 {
1041 const char *apszArgv[6];
1042 apszArgv[0] = g_szSvnPath;
1043 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel";
1044 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
1045 int iArg = 3;
1046 if (pState->paSvnPropChanges[i].pszValue)
1047 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
1048 apszArgv[iArg++] = pState->pszFilename;
1049 apszArgv[iArg++] = NULL;
1050
1051 int rc = scmSvnRun(pState, apszArgv, false);
1052 if (RT_FAILURE(rc))
1053 return rc;
1054 }
1055
1056 return VINF_SUCCESS;
1057#else
1058 return VERR_NOT_IMPLEMENTED;
1059#endif
1060}
1061
1062
1063
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