VirtualBox

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

Last change on this file since 56330 was 56330, checked in by vboxsync, 9 years ago

scmsubversion.cpp: ppszValue can be NULL.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.9 KB
Line 
1/* $Id: scmsubversion.cpp 56330 2015-06-10 11:22:54Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
4 */
5
6/*
7 * Copyright (C) 2010-2015 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_WITH_DYNAMIC_LIB_SVN
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/err.h>
28#include <iprt/file.h>
29#include <iprt/getopt.h>
30#include <iprt/handle.h>
31#include <iprt/initterm.h>
32#include <iprt/ldr.h>
33#include <iprt/mem.h>
34#include <iprt/message.h>
35#include <iprt/param.h>
36#include <iprt/path.h>
37#include <iprt/pipe.h>
38#include <iprt/poll.h>
39#include <iprt/process.h>
40#include <iprt/stream.h>
41#include <iprt/string.h>
42
43#include "scm.h"
44
45#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && defined(SCM_WITH_SVN_HEADERS)
46# include <svn_client.h>
47#endif
48
49
50/*******************************************************************************
51* Defined Constants And Macros *
52*******************************************************************************/
53#ifdef SCM_WITH_DYNAMIC_LIB_SVN
54# if defined(RT_OS_WINDOWS) && defined(RT_ARCH_X86)
55# define APR_CALL __stdcall
56# define SVN_CALL /* __stdcall ?? */
57# else
58# define APR_CALL
59# define SVN_CALL
60# endif
61#endif
62#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
63# define SVN_ERR_MISC_CATEGORY_START 200000
64# define SVN_ERR_UNVERSIONED_RESOURCE (SVN_ERR_MISC_CATEGORY_START + 5)
65#endif
66
67
68/*******************************************************************************
69* Structures and Typedefs *
70*******************************************************************************/
71#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
72typedef int apr_status_t;
73typedef int64_t apr_time_t;
74typedef struct apr_pool_t apr_pool_t;
75typedef struct apr_hash_t apr_hash_t;
76typedef struct apr_hash_index_t apr_hash_index_t;
77typedef struct apr_array_header_t apr_array_header_t;
78
79
80typedef struct svn_error_t
81{
82 apr_status_t apr_err;
83 const char *_dbgr_message;
84 struct svn_error_t *_dbgr_child;
85 apr_pool_t *_dbgr_pool;
86 const char *_dbgr_file;
87 long _dbgr_line;
88} svn_error_t;
89typedef int svn_boolean_t;
90typedef long int svn_revnum_t;
91typedef struct svn_client_ctx_t svn_client_ctx_t;
92typedef enum svn_opt_revision_kind
93{
94 svn_opt_revision_unspecified = 0,
95 svn_opt_revision_number,
96 svn_opt_revision_date,
97 svn_opt_revision_committed,
98 svn_opt_revision_previous,
99 svn_opt_revision_base,
100 svn_opt_revision_working,
101 svn_opt_revision_head
102} svn_opt_revision_kind;
103typedef union svn_opt_revision_value_t
104{
105 svn_revnum_t number;
106 apr_time_t date;
107} svn_opt_revision_value_t;
108typedef struct svn_opt_revision_t
109{
110 svn_opt_revision_kind kind;
111 svn_opt_revision_value_t value;
112} svn_opt_revision_t;
113typedef enum svn_depth_t
114{
115 svn_depth_unknown = -2,
116 svn_depth_exclude,
117 svn_depth_empty,
118 svn_depth_files,
119 svn_depth_immediates,
120 svn_depth_infinity
121} svn_depth_t;
122
123#endif /* SCM_WITH_DYNAMIC_LIB_SVN && !SCM_WITH_SVN_HEADERS */
124
125
126/*******************************************************************************
127* Global Variables *
128*******************************************************************************/
129static char g_szSvnPath[RTPATH_MAX];
130static enum
131{
132 kScmSvnVersion_Ancient = 1,
133 kScmSvnVersion_1_6,
134 kScmSvnVersion_1_7,
135 kScmSvnVersion_1_8,
136 kScmSvnVersion_End
137} g_enmSvnVersion = kScmSvnVersion_Ancient;
138
139
140#ifdef SCM_WITH_DYNAMIC_LIB_SVN
141/** Set if all the function pointers are valid. */
142static bool g_fSvnFunctionPointersValid;
143/** @name SVN and APR imports.
144 * @{ */
145static apr_status_t (APR_CALL *g_pfnAprInitialize)(void);
146static apr_hash_index_t * (APR_CALL *g_pfnAprHashFirst)(apr_pool_t *pPool, apr_hash_t *pHashTab);
147static apr_hash_index_t * (APR_CALL *g_pfnAprHashNext)(apr_hash_index_t *pCurIdx);
148static void * (APR_CALL *g_pfnAprHashThisVal)(apr_hash_index_t *pHashIdx);
149static apr_pool_t * (SVN_CALL *g_pfnSvnPoolCreateEx)(apr_pool_t *pParent, void *pvAllocator);
150static void (APR_CALL *g_pfnAprPoolClear)(apr_pool_t *pPool);
151static void (APR_CALL *g_pfnAprPoolDestroy)(apr_pool_t *pPool);
152
153static svn_error_t * (SVN_CALL *g_pfnSvnClientCreateContext)(svn_client_ctx_t **ppCtx, apr_pool_t *pPool);
154static svn_error_t * (SVN_CALL *g_pfnSvnClientPropGet4)(apr_hash_t **ppHashProps, const char *pszPropName,
155 const char *pszTarget, const svn_opt_revision_t *pPeggedRev,
156 const svn_opt_revision_t *pRevision, svn_revnum_t *pActualRev,
157 svn_depth_t enmDepth, const apr_array_header_t *pChangeList,
158 svn_client_ctx_t *pCtx, apr_pool_t *pResultPool,
159 apr_pool_t *pScratchPool);
160/**@} */
161#endif
162
163
164
165/**
166 * Callback that is call for each path to search.
167 */
168static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
169{
170 char *pszDst = (char *)pvUser1;
171 size_t cchDst = (size_t)pvUser2;
172 if (cchDst > cchPath)
173 {
174 memcpy(pszDst, pchPath, cchPath);
175 pszDst[cchPath] = '\0';
176#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
177 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
178#else
179 int rc = RTPathAppend(pszDst, cchDst, "svn");
180#endif
181 if ( RT_SUCCESS(rc)
182 && RTFileExists(pszDst))
183 return VINF_SUCCESS;
184 }
185 return VERR_TRY_AGAIN;
186}
187
188
189/**
190 * Reads from a pipe.
191 *
192 * @returns @a rc or other status code.
193 * @param rc The current status of the operation. Error status
194 * are preserved and returned.
195 * @param phPipeR Pointer to the pipe handle.
196 * @param pcbAllocated Pointer to the buffer size variable.
197 * @param poffCur Pointer to the buffer offset variable.
198 * @param ppszBuffer Pointer to the buffer pointer variable.
199 */
200static int rtProcProcessOutput(int rc, PRTPIPE phPipeR, size_t *pcbAllocated, size_t *poffCur, char **ppszBuffer,
201 RTPOLLSET hPollSet, uint32_t idPollSet)
202{
203 size_t cbRead;
204 char szTmp[_4K - 1];
205 for (;;)
206 {
207 int rc2 = RTPipeRead(*phPipeR, szTmp, sizeof(szTmp), &cbRead);
208 if (RT_SUCCESS(rc2) && cbRead)
209 {
210 /* Resize the buffer. */
211 if (*poffCur + cbRead >= *pcbAllocated)
212 {
213 if (*pcbAllocated >= _1G)
214 {
215 RTPollSetRemove(hPollSet, idPollSet);
216 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
217 *phPipeR = NIL_RTPIPE;
218 return RT_SUCCESS(rc) ? VERR_TOO_MUCH_DATA : rc;
219 }
220
221 size_t cbNew = *pcbAllocated ? *pcbAllocated * 2 : sizeof(szTmp) + 1;
222 Assert(*poffCur + cbRead < cbNew);
223 rc2 = RTStrRealloc(ppszBuffer, cbNew);
224 if (RT_FAILURE(rc2))
225 {
226 RTPollSetRemove(hPollSet, idPollSet);
227 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
228 *phPipeR = NIL_RTPIPE;
229 return RT_SUCCESS(rc) ? rc2 : rc;
230 }
231 *pcbAllocated = cbNew;
232 }
233
234 /* Append the new data, terminating it. */
235 memcpy(*ppszBuffer + *poffCur, szTmp, cbRead);
236 *poffCur += cbRead;
237 (*ppszBuffer)[*poffCur] = '\0';
238
239 /* Check for null terminators in the string. */
240 if (RT_SUCCESS(rc) && memchr(szTmp, '\0', cbRead))
241 rc = VERR_NO_TRANSLATION;
242
243 /* If we read a full buffer, try read some more. */
244 if (RT_SUCCESS(rc) && cbRead == sizeof(szTmp))
245 continue;
246 }
247 else if (rc2 != VINF_TRY_AGAIN)
248 {
249 if (RT_FAILURE(rc) && rc2 != VERR_BROKEN_PIPE)
250 rc = rc2;
251 RTPollSetRemove(hPollSet, idPollSet);
252 rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
253 *phPipeR = NIL_RTPIPE;
254 }
255 return rc;
256 }
257}
258
259/** @name RTPROCEXEC_FLAGS_XXX - flags for RTProcExec and RTProcExecToString.
260 * @{ */
261/** Redirect /dev/null to standard input. */
262#define RTPROCEXEC_FLAGS_STDIN_NULL RT_BIT_32(0)
263/** Redirect standard output to /dev/null. */
264#define RTPROCEXEC_FLAGS_STDOUT_NULL RT_BIT_32(1)
265/** Redirect standard error to /dev/null. */
266#define RTPROCEXEC_FLAGS_STDERR_NULL RT_BIT_32(2)
267/** Redirect all standard output to /dev/null as well as directing /dev/null
268 * to standard input. */
269#define RTPROCEXEC_FLAGS_STD_NULL ( RTPROCEXEC_FLAGS_STDIN_NULL \
270 | RTPROCEXEC_FLAGS_STDOUT_NULL \
271 | RTPROCEXEC_FLAGS_STDERR_NULL)
272/** Mask containing the valid flags. */
273#define RTPROCEXEC_FLAGS_VALID_MASK UINT32_C(0x00000007)
274/** @} */
275
276/**
277 * Runs a process, collecting the standard output and/or standard error.
278 *
279 *
280 * @returns IPRT status code
281 * @retval VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8
282 * or contains a nul character.
283 * @retval VERR_TOO_MUCH_DATA if the process produced too much data.
284 *
285 * @param pszExec Executable image to use to create the child process.
286 * @param papszArgs Pointer to an array of arguments to the child. The
287 * array terminated by an entry containing NULL.
288 * @param hEnv Handle to the environment block for the child.
289 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. The @a
290 * ppszStdOut and @a ppszStdErr parameters takes precedence
291 * over redirection flags.
292 * @param pStatus Where to return the status on success.
293 * @param ppszStdOut Where to return the text written to standard output. If
294 * NULL then standard output will not be collected and go
295 * to the standard output handle of the process.
296 * Free with RTStrFree, regardless of return status.
297 * @param ppszStdErr Where to return the text written to standard error. If
298 * NULL then standard output will not be collected and go
299 * to the standard error handle of the process.
300 * Free with RTStrFree, regardless of return status.
301 */
302int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
303 PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr)
304{
305 int rc2;
306
307 /*
308 * Clear output arguments (no returning failure here, simply crash!).
309 */
310 AssertPtr(pStatus);
311 pStatus->enmReason = RTPROCEXITREASON_ABEND;
312 pStatus->iStatus = RTEXITCODE_FAILURE;
313 AssertPtrNull(ppszStdOut);
314 if (ppszStdOut)
315 *ppszStdOut = NULL;
316 AssertPtrNull(ppszStdOut);
317 if (ppszStdErr)
318 *ppszStdErr = NULL;
319
320 /*
321 * Check input arguments.
322 */
323 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
324
325 /*
326 * Do we need a standard input bitbucket?
327 */
328 int rc = VINF_SUCCESS;
329 PRTHANDLE phChildStdIn = NULL;
330 RTHANDLE hChildStdIn;
331 hChildStdIn.enmType = RTHANDLETYPE_FILE;
332 hChildStdIn.u.hFile = NIL_RTFILE;
333 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
334 {
335 phChildStdIn = &hChildStdIn;
336 rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ);
337 }
338
339 /*
340 * Create the output pipes / bitbuckets.
341 */
342 RTPIPE hPipeStdOutR = NIL_RTPIPE;
343 PRTHANDLE phChildStdOut = NULL;
344 RTHANDLE hChildStdOut;
345 hChildStdOut.enmType = RTHANDLETYPE_PIPE;
346 hChildStdOut.u.hPipe = NIL_RTPIPE;
347 if (ppszStdOut && RT_SUCCESS(rc))
348 {
349 phChildStdOut = &hChildStdOut;
350 rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/);
351 }
352 else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
353 {
354 phChildStdOut = &hChildStdOut;
355 hChildStdOut.enmType = RTHANDLETYPE_FILE;
356 hChildStdOut.u.hFile = NIL_RTFILE;
357 rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE);
358 }
359
360 RTPIPE hPipeStdErrR = NIL_RTPIPE;
361 PRTHANDLE phChildStdErr = NULL;
362 RTHANDLE hChildStdErr;
363 hChildStdErr.enmType = RTHANDLETYPE_PIPE;
364 hChildStdErr.u.hPipe = NIL_RTPIPE;
365 if (ppszStdErr && RT_SUCCESS(rc))
366 {
367 phChildStdErr = &hChildStdErr;
368 rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/);
369 }
370 else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
371 {
372 phChildStdErr = &hChildStdErr;
373 hChildStdErr.enmType = RTHANDLETYPE_FILE;
374 hChildStdErr.u.hFile = NIL_RTFILE;
375 rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE);
376 }
377
378 if (RT_SUCCESS(rc))
379 {
380 RTPOLLSET hPollSet;
381 rc = RTPollSetCreate(&hPollSet);
382 if (RT_SUCCESS(rc))
383 {
384 if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc))
385 rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1);
386 if (hPipeStdErrR != NIL_RTPIPE)
387 rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2);
388 }
389 if (RT_SUCCESS(rc))
390 {
391 /*
392 * Create the process.
393 */
394 RTPROCESS hProc;
395 rc = RTProcCreateEx(g_szSvnPath,
396 papszArgs,
397 RTENV_DEFAULT,
398 0 /*fFlags*/,
399 NULL /*phStdIn*/,
400 phChildStdOut,
401 phChildStdErr,
402 NULL /*pszAsUser*/,
403 NULL /*pszPassword*/,
404 &hProc);
405 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
406 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
407
408 if (RT_SUCCESS(rc))
409 {
410 /*
411 * Process output and wait for the process to finish.
412 */
413 size_t cbStdOut = 0;
414 size_t offStdOut = 0;
415 size_t cbStdErr = 0;
416 size_t offStdErr = 0;
417 for (;;)
418 {
419 if (hPipeStdOutR != NIL_RTPIPE)
420 rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
421 if (hPipeStdErrR != NIL_RTPIPE)
422 rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
423 if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
424 break;
425
426 if (hProc != NIL_RTPROCESS)
427 {
428 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
429 if (rc2 != VERR_PROCESS_RUNNING)
430 {
431 if (RT_FAILURE(rc2))
432 rc = rc2;
433 hProc = NIL_RTPROCESS;
434 }
435 }
436
437 rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
438 Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
439 }
440
441 if (RT_SUCCESS(rc))
442 {
443 if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
444 || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
445 rc = VERR_NO_TRANSLATION;
446 }
447
448 /*
449 * No more output, just wait for it to finish.
450 */
451 if (hProc != NIL_RTPROCESS)
452 {
453 rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
454 if (RT_FAILURE(rc2))
455 rc = rc2;
456 }
457 }
458 RTPollSetDestroy(hPollSet);
459 }
460 }
461
462 rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
463 rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
464 rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2);
465 rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2);
466 rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2);
467 return rc;
468}
469
470
471/**
472 * Runs a process, waiting for it to complete.
473 *
474 * @returns IPRT status code
475 *
476 * @param pszExec Executable image to use to create the child process.
477 * @param papszArgs Pointer to an array of arguments to the child. The
478 * array terminated by an entry containing NULL.
479 * @param hEnv Handle to the environment block for the child.
480 * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX.
481 * @param pStatus Where to return the status on success.
482 */
483int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
484 PRTPROCSTATUS pStatus)
485{
486 int rc;
487
488 /*
489 * Clear output argument (no returning failure here, simply crash!).
490 */
491 AssertPtr(pStatus);
492 pStatus->enmReason = RTPROCEXITREASON_ABEND;
493 pStatus->iStatus = RTEXITCODE_FAILURE;
494
495 /*
496 * Check input arguments.
497 */
498 AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
499
500 /*
501 * Set up /dev/null redirections.
502 */
503 PRTHANDLE aph[3] = { NULL, NULL, NULL };
504 RTHANDLE ah[3];
505 for (uint32_t i = 0; i < 3; i++)
506 {
507 ah[i].enmType = RTHANDLETYPE_FILE;
508 ah[i].u.hFile = NIL_RTFILE;
509 }
510 rc = VINF_SUCCESS;
511 if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
512 {
513 aph[0] = &ah[0];
514 rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
515 }
516 if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
517 {
518 aph[1] = &ah[1];
519 rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
520 }
521 if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
522 {
523 aph[2] = &ah[2];
524 rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
525 }
526
527 /*
528 * Create the process.
529 */
530 RTPROCESS hProc;
531 if (RT_SUCCESS(rc))
532 rc = RTProcCreateEx(g_szSvnPath,
533 papszArgs,
534 RTENV_DEFAULT,
535 0 /*fFlags*/,
536 aph[0],
537 aph[1],
538 aph[2],
539 NULL /*pszAsUser*/,
540 NULL /*pszPassword*/,
541 &hProc);
542
543 for (uint32_t i = 0; i < 3; i++)
544 RTFileClose(ah[i].u.hFile);
545
546 if (RT_SUCCESS(rc))
547 rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
548 return rc;
549}
550
551
552
553/**
554 * Executes SVN and gets the output.
555 *
556 * Standard error is suppressed.
557 *
558 * @returns VINF_SUCCESS if the command executed successfully.
559 * @param pState The rewrite state to work on. Can be NULL.
560 * @param papszArgs The SVN argument.
561 * @param fNormalFailureOk Whether normal failure is ok.
562 * @param ppszStdOut Where to return the output on success.
563 */
564static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut)
565{
566 *ppszStdOut = NULL;
567
568 char *pszCmdLine = NULL;
569 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
570 if (RT_FAILURE(rc))
571 return rc;
572 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
573
574 RTPROCSTATUS Status;
575 rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT,
576 RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL);
577
578 if ( RT_SUCCESS(rc)
579 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
580 || Status.iStatus != 0) )
581 {
582 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
583 RTMsgError("%s: %s -> %s %u\n",
584 pszCmdLine,
585 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
586 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
587 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
588 : "abducted by alien",
589 Status.iStatus);
590 rc = VERR_GENERAL_FAILURE;
591 }
592 else if (RT_FAILURE(rc))
593 {
594 if (pState)
595 RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc);
596 else
597 RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc);
598 }
599
600 if (RT_FAILURE(rc))
601 {
602 RTStrFree(*ppszStdOut);
603 *ppszStdOut = NULL;
604 }
605 RTStrFree(pszCmdLine);
606 return rc;
607}
608
609
610/**
611 * Executes SVN.
612 *
613 * Standard error and standard output is suppressed.
614 *
615 * @returns VINF_SUCCESS if the command executed successfully.
616 * @param pState The rewrite state to work on.
617 * @param papszArgs The SVN argument.
618 * @param fNormalFailureOk Whether normal failure is ok.
619 */
620static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk)
621{
622 char *pszCmdLine = NULL;
623 int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
624 if (RT_FAILURE(rc))
625 return rc;
626 ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
627
628 /* Lazy bird uses RTProcExecToString. */
629 RTPROCSTATUS Status;
630 rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status);
631
632 if ( RT_SUCCESS(rc)
633 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
634 || Status.iStatus != 0) )
635 {
636 if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
637 RTMsgError("%s: %s -> %s %u\n",
638 pState->pszFilename,
639 pszCmdLine,
640 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
641 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
642 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
643 : "abducted by alien",
644 Status.iStatus);
645 rc = VERR_GENERAL_FAILURE;
646 }
647 else if (RT_FAILURE(rc))
648 RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc);
649
650 RTStrFree(pszCmdLine);
651 return rc;
652}
653
654
655#ifdef SCM_WITH_DYNAMIC_LIB_SVN
656/**
657 * Attempts to resolve the necessary subversion and apache portable runtime APIs
658 * we require dynamically.
659 *
660 * Will set all global function pointers and g_fSvnFunctionPointersValid to true
661 * on success.
662 */
663static void scmSvnTryResolveFunctions(void)
664{
665 char szPath[RTPATH_MAX];
666 int rc = RTStrCopy(szPath, sizeof(szPath), g_szSvnPath);
667 if (RT_SUCCESS(rc))
668 {
669 RTPathStripFilename(szPath);
670 char *pszEndPath = strchr(szPath, '\0');
671# ifdef RT_OS_WINDOWS
672 RTPathChangeToDosSlashes(szPath, false);
673# endif
674
675 /*
676 * Try various prefixes/suffxies/locations.
677 */
678 static struct
679 {
680 const char *pszPrefix;
681 const char *pszSuffix;
682 } const s_aVariations[] =
683 {
684# ifdef RT_OS_WINDOWS
685 { "SlikSvn-lib", "-1.dll" }, /* SlikSVN */
686 { "lib", "-1.dll" }, /* Win32Svn,CollabNet,++ */
687# elif defined(RT_OS_DARWIN)
688 { "../lib/lib", "-1.dylib" },
689# else
690 { "../lib/lib", ".so" },
691 { "../lib/lib", "-1.so" },
692# endif
693 };
694 for (unsigned iVar = 0; RT_ELEMENTS(s_aVariations); iVar++)
695 {
696 /*
697 * Try load the svn_client library ...
698 */
699 struct
700 {
701 const char *pszBaseName;
702 RTLDRMOD hMod;
703 } aLibraries[] =
704 {
705 { "svn_client", NIL_RTLDRMOD },
706 { "svn_subr", NIL_RTLDRMOD },
707 { "apr", NIL_RTLDRMOD },
708 };
709 rc = VINF_SUCCESS;
710 unsigned iLib;
711 for (iLib = 0; iLib < RT_ELEMENTS(aLibraries) && RT_SUCCESS(rc); iLib++)
712 {
713 *pszEndPath = '\0';
714 rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar].pszPrefix);
715 if (RT_SUCCESS(rc))
716 rc = RTStrCat(szPath, sizeof(szPath), aLibraries[iLib].pszBaseName);
717 if (RT_SUCCESS(rc))
718 rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar].pszSuffix);
719 if (RT_SUCCESS(rc))
720 {
721# ifdef RT_OS_WINDOWS
722 RTPathChangeToDosSlashes(pszEndPath, false);
723# endif
724 rc = RTLdrLoadEx(szPath, &aLibraries[iLib].hMod, RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR , NULL);
725 }
726 }
727 if (iLib == RT_ELEMENTS(aLibraries) && RT_SUCCESS(rc))
728 {
729 static const struct
730 {
731 unsigned iLib;
732 const char *pszSymbol;
733 PFNRT *ppfn;
734 } s_aSymbols[] =
735 {
736 { 2, "apr_initialize", (PFNRT *)&g_pfnAprInitialize },
737 { 2, "apr_hash_first", (PFNRT *)&g_pfnAprHashFirst },
738 { 2, "apr_hash_next", (PFNRT *)&g_pfnAprHashNext },
739 { 2, "apr_hash_this_val", (PFNRT *)&g_pfnAprHashThisVal },
740 { 1, "svn_pool_create_ex", (PFNRT *)&g_pfnSvnPoolCreateEx },
741 { 2, "apr_pool_clear", (PFNRT *)&g_pfnAprPoolClear },
742 { 2, "apr_pool_destroy", (PFNRT *)&g_pfnAprPoolDestroy },
743 { 0, "svn_client_create_context", (PFNRT *)&g_pfnSvnClientCreateContext },
744 { 0, "svn_client_propget4", (PFNRT *)&g_pfnSvnClientPropGet4 },
745 };
746 for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
747 {
748 rc = RTLdrGetSymbol(aLibraries[s_aSymbols[i].iLib].hMod, s_aSymbols[i].pszSymbol,
749 (void **)(uintptr_t)s_aSymbols[i].ppfn);
750 if (RT_FAILURE(rc))
751 {
752 ScmVerbose(NULL, 0, "Failed to resolve '%s' in '%s'",
753 s_aSymbols[i].pszSymbol, aLibraries[s_aSymbols[i].iLib].pszBaseName);
754 break;
755 }
756 }
757 if (RT_SUCCESS(rc))
758 {
759 apr_status_t rc = g_pfnAprInitialize();
760 if (rc == 0)
761 {
762 ScmVerbose(NULL, 1, "Found subversion APIs.\n");
763 g_fSvnFunctionPointersValid = true;
764 }
765 else
766 {
767 ScmVerbose(NULL, 0, "apr_initialize failed: %#x (%d)\n", rc, rc);
768 AssertMsgFailed(("%#x (%d)\n", rc, rc));
769 }
770 return;
771 }
772 }
773
774 while (iLib-- > 0)
775 RTLdrClose(aLibraries[iLib].hMod);
776 }
777 }
778}
779#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
780
781
782/**
783 * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion.
784 */
785static void scmSvnFindSvnBinary(PSCMRWSTATE pState)
786{
787 /* Already been called? */
788 if (g_szSvnPath[0] != '\0')
789 return;
790
791 /*
792 * Locate it.
793 */
794 /** @todo code page fun... */
795#ifdef RT_OS_WINDOWS
796 const char *pszEnvVar = RTEnvGet("Path");
797#else
798 const char *pszEnvVar = RTEnvGet("PATH");
799#endif
800 if (pszEnvVar)
801 {
802#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
803 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
804#else
805 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
806#endif
807 if (RT_FAILURE(rc))
808 strcpy(g_szSvnPath, "svn");
809 }
810 else
811 strcpy(g_szSvnPath, "svn");
812
813 /*
814 * Check the version.
815 */
816 const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL };
817 char *pszVersion;
818 int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion);
819 if (RT_SUCCESS(rc))
820 {
821 char *pszStripped = RTStrStrip(pszVersion);
822 if (RTStrVersionCompare(pszVersion, "1.8") >= 0)
823 g_enmSvnVersion = kScmSvnVersion_1_8;
824 else if (RTStrVersionCompare(pszVersion, "1.7") >= 0)
825 g_enmSvnVersion = kScmSvnVersion_1_7;
826 else if (RTStrVersionCompare(pszVersion, "1.6") >= 0)
827 g_enmSvnVersion = kScmSvnVersion_1_6;
828 else
829 g_enmSvnVersion = kScmSvnVersion_Ancient;
830 RTStrFree(pszVersion);
831 }
832 else
833 g_enmSvnVersion = kScmSvnVersion_Ancient;
834
835#ifdef SCM_WITH_DYNAMIC_LIB_SVN
836 /*
837 * If we got version 1.8 or later, try see if we can locate a few of the
838 * simpler SVN APIs.
839 */
840 g_fSvnFunctionPointersValid = false;
841 if (g_enmSvnVersion >= kScmSvnVersion_1_8)
842 scmSvnTryResolveFunctions();
843#endif
844}
845
846
847/**
848 * Construct a dot svn filename for the file being rewritten.
849 *
850 * @returns IPRT status code.
851 * @param pState The rewrite state (for the name).
852 * @param pszDir The directory, including ".svn/".
853 * @param pszSuff The filename suffix.
854 * @param pszDst The output buffer. RTPATH_MAX in size.
855 */
856static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
857{
858 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
859 RTPathStripFilename(pszDst);
860
861 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
862 if (RT_SUCCESS(rc))
863 {
864 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
865 if (RT_SUCCESS(rc))
866 {
867 size_t cchDst = strlen(pszDst);
868 size_t cchSuff = strlen(pszSuff);
869 if (cchDst + cchSuff < RTPATH_MAX)
870 {
871 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
872 return VINF_SUCCESS;
873 }
874 else
875 rc = VERR_BUFFER_OVERFLOW;
876 }
877 }
878 return rc;
879}
880
881/**
882 * Interprets the specified string as decimal numbers.
883 *
884 * @returns true if parsed successfully, false if not.
885 * @param pch The string (not terminated).
886 * @param cch The string length.
887 * @param pu Where to return the value.
888 */
889static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
890{
891 size_t u = 0;
892 while (cch-- > 0)
893 {
894 char ch = *pch++;
895 if (ch < '0' || ch > '9')
896 return false;
897 u *= 10;
898 u += ch - '0';
899 }
900 *pu = u;
901 return true;
902}
903
904
905#ifdef SCM_WITH_DYNAMIC_LIB_SVN
906
907/**
908 * Wrapper around RTPathAbs.
909 * @returns Same as RTPathAbs.
910 * @param pszPath The relative path.
911 * @param pszAbsPath Where to return the absolute path.
912 * @param cbAbsPath Size of the @a pszAbsPath buffer.
913 */
914static int scmSvnAbsPath(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
915{
916 int rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPath);
917# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
918 if (RT_SUCCESS(rc))
919 RTPathChangeToUnixSlashes(pszAbsPath, true /*fForce*/);
920# endif
921 return rc;
922}
923
924
925/**
926 * Checks if @a pszPath exists in the current WC.
927 *
928 * @returns true, false or -1. In the latter case, please use the fallback.
929 * @param pszPath Path to the object that should be investigated.
930 */
931static int scmSvnIsObjectInWorkingCopy(const char *pszPath)
932{
933 int rc = -1;
934
935 /* svn_client_propget4 and later requires absolute target path. */
936 char szAbsPath[RTPATH_MAX];
937 int rc2 = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
938 if (RT_SUCCESS(rc2))
939 {
940 /* Create calling context. */
941 apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL);
942 if (pPool)
943 {
944 svn_client_ctx_t *pCtx = NULL;
945 svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool);
946 if (!pErr)
947 {
948 /* Make the call. */
949 apr_hash_t *pHash = NULL;
950 svn_opt_revision_t Rev;
951 RT_ZERO(Rev);
952 Rev.kind = svn_opt_revision_base;
953 Rev.value.number = -1L;
954 pErr = g_pfnSvnClientPropGet4(&pHash, "svn:no-such-property", szAbsPath, &Rev, &Rev,
955 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/, pCtx, pPool, pPool);
956 if (!pErr)
957 rc = true;
958 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
959 rc = false;
960 }
961 g_pfnAprPoolDestroy(pPool);
962 }
963 }
964 return rc;
965}
966
967#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
968
969
970/**
971 * Checks if the file we're operating on is part of a SVN working copy.
972 *
973 * @returns true if it is, false if it isn't or we cannot tell.
974 * @param pState The rewrite state to work on.
975 */
976bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
977{
978 scmSvnFindSvnBinary(pState);
979
980#ifdef SCM_WITH_DYNAMIC_LIB_SVN
981 if (g_fSvnFunctionPointersValid)
982 {
983 int rc = scmSvnIsObjectInWorkingCopy(pState->pszFilename);
984 if (rc == (int)true || rc == (int)false)
985 return rc == (int)true;
986 }
987
988 /* Fallback: */
989#endif
990 if (g_enmSvnVersion < kScmSvnVersion_1_7)
991 {
992 /*
993 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
994 */
995 char szPath[RTPATH_MAX];
996 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
997 if (RT_SUCCESS(rc))
998 return RTFileExists(szPath);
999 }
1000 else
1001 {
1002 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pState->pszFilename, NULL };
1003 char *pszValue;
1004 int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
1005 if (RT_SUCCESS(rc))
1006 {
1007 RTStrFree(pszValue);
1008 return true;
1009 }
1010 }
1011 return false;
1012}
1013
1014
1015/**
1016 * Checks if the specified directory is part of a SVN working copy.
1017 *
1018 * @returns true if it is, false if it isn't or we cannot tell.
1019 * @param pszDir The directory in question.
1020 */
1021bool ScmSvnIsDirInWorkingCopy(const char *pszDir)
1022{
1023 scmSvnFindSvnBinary(NULL);
1024
1025#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1026 if (g_fSvnFunctionPointersValid)
1027 {
1028 int rc = scmSvnIsObjectInWorkingCopy(pszDir);
1029 if (rc == (int)true || rc == (int)false)
1030 return rc == (int)true;
1031 }
1032
1033 /* Fallback: */
1034#endif
1035 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1036 {
1037 /*
1038 * Hack: check if the .svn/ dir exists.
1039 */
1040 char szPath[RTPATH_MAX];
1041 int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn");
1042 if (RT_SUCCESS(rc))
1043 return RTDirExists(szPath);
1044 }
1045 else
1046 {
1047 const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL };
1048 char *pszValue;
1049 int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue);
1050 if (RT_SUCCESS(rc))
1051 {
1052 RTStrFree(pszValue);
1053 return true;
1054 }
1055 }
1056 return false;
1057}
1058
1059
1060#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1061/**
1062 * Checks if @a pszPath exists in the current WC.
1063 *
1064 * @returns IPRT status code - VERR_NOT_SUPPORT if fallback should be attempted.
1065 * @param pszPath Path to the object that should be investigated.
1066 * @param pszProperty The property name.
1067 * @param ppszValue Where to return the property value. Optional.
1068 */
1069static int scmSvnQueryPropertyUsingApi(const char *pszPath, const char *pszProperty, char **ppszValue)
1070{
1071 int rc = VERR_NOT_SUPPORTED;
1072
1073 /* svn_client_propget4 and later requires absolute target path. */
1074 char szAbsPath[RTPATH_MAX];
1075 int rc2 = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
1076 if (RT_SUCCESS(rc2))
1077 {
1078 /* Create calling context. */
1079 apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL);
1080 if (pPool)
1081 {
1082 svn_client_ctx_t *pCtx = NULL;
1083 svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool);
1084 if (!pErr)
1085 {
1086 /* Make the call. */
1087 apr_hash_t *pHash = NULL;
1088 svn_opt_revision_t Rev;
1089 RT_ZERO(Rev);
1090 Rev.kind = svn_opt_revision_base;
1091 Rev.value.number = -1L;
1092 pErr = g_pfnSvnClientPropGet4(&pHash, pszProperty, szAbsPath, &Rev, &Rev,
1093 NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/, pCtx, pPool, pPool);
1094 if (!pErr)
1095 {
1096 /* Get the first value, if any. */
1097 rc = VERR_NOT_FOUND;
1098 apr_hash_index_t *pHashIdx = g_pfnAprHashFirst(pPool, pHash);
1099 if (pHashIdx)
1100 {
1101 const char **ppszFirst = (const char **)g_pfnAprHashThisVal(pHashIdx);
1102 if (ppszFirst && *ppszFirst)
1103 {
1104 if (ppszValue)
1105 rc = RTStrDupEx(ppszValue, *ppszFirst);
1106 else
1107 rc = VINF_SUCCESS;
1108 }
1109 }
1110 }
1111 else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
1112 rc = VERR_INVALID_STATE;
1113 else
1114 rc = VERR_GENERAL_FAILURE;
1115 }
1116 g_pfnAprPoolDestroy(pPool);
1117 }
1118 }
1119 return rc;
1120}
1121#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
1122
1123
1124/**
1125 * Queries the value of an SVN property.
1126 *
1127 * This will automatically adjust for scheduled changes.
1128 *
1129 * @returns IPRT status code.
1130 * @retval VERR_INVALID_STATE if not a SVN WC file.
1131 * @retval VERR_NOT_FOUND if the property wasn't found.
1132 * @param pState The rewrite state to work on.
1133 * @param pszName The property name.
1134 * @param ppszValue Where to return the property value. Free this
1135 * using RTStrFree. Optional.
1136 */
1137int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
1138{
1139 int rc;
1140
1141 /*
1142 * Look it up in the scheduled changes.
1143 */
1144 size_t i = pState->cSvnPropChanges;
1145 while (i-- > 0)
1146 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1147 {
1148 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1149 if (!pszValue)
1150 return VERR_NOT_FOUND;
1151 if (ppszValue)
1152 return RTStrDupEx(ppszValue, pszValue);
1153 return VINF_SUCCESS;
1154 }
1155
1156 scmSvnFindSvnBinary(pState);
1157
1158#ifdef SCM_WITH_DYNAMIC_LIB_SVN
1159 if (g_fSvnFunctionPointersValid)
1160 {
1161 rc = scmSvnQueryPropertyUsingApi(pState->pszFilename, pszName, ppszValue);
1162 if (rc != VERR_NOT_SUPPORTED)
1163 return rc;
1164 /* Fallback: */
1165 }
1166#endif
1167
1168 if (g_enmSvnVersion < kScmSvnVersion_1_7)
1169 {
1170 /*
1171 * Hack: Read the .svn/props/<file>.svn-work file exists.
1172 */
1173 char szPath[RTPATH_MAX];
1174 rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
1175 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
1176 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
1177 if (RT_SUCCESS(rc))
1178 {
1179 SCMSTREAM Stream;
1180 rc = ScmStreamInitForReading(&Stream, szPath);
1181 if (RT_SUCCESS(rc))
1182 {
1183 /*
1184 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
1185 */
1186 rc = VERR_NOT_FOUND;
1187 size_t const cchName = strlen(pszName);
1188 SCMEOL enmEol;
1189 size_t cchLine;
1190 const char *pchLine;
1191 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
1192 {
1193 /*
1194 * Parse the 'K num' / 'END' line.
1195 */
1196 if ( cchLine == 3
1197 && !memcmp(pchLine, "END", 3))
1198 break;
1199 size_t cchKey;
1200 if ( cchLine < 3
1201 || pchLine[0] != 'K'
1202 || pchLine[1] != ' '
1203 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
1204 || cchKey == 0
1205 || cchKey > 4096)
1206 {
1207 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1208 rc = VERR_PARSE_ERROR;
1209 break;
1210 }
1211
1212 /*
1213 * Match the key and skip to the value line. Don't bother with
1214 * names containing EOL markers.
1215 */
1216 size_t const offKey = ScmStreamTell(&Stream);
1217 bool fMatch = cchName == cchKey;
1218 if (fMatch)
1219 {
1220 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1221 if (!pchLine)
1222 break;
1223 fMatch = cchLine == cchName
1224 && !memcmp(pchLine, pszName, cchName);
1225 }
1226
1227 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
1228 break;
1229 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1230 break;
1231
1232 /*
1233 * Read and Parse the 'V num' line.
1234 */
1235 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
1236 if (!pchLine)
1237 break;
1238 size_t cchValue;
1239 if ( cchLine < 3
1240 || pchLine[0] != 'V'
1241 || pchLine[1] != ' '
1242 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
1243 || cchValue > _1M)
1244 {
1245 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
1246 rc = VERR_PARSE_ERROR;
1247 break;
1248 }
1249
1250 /*
1251 * If we have a match, allocate a return buffer and read the
1252 * value into it. Otherwise skip this value and continue
1253 * searching.
1254 */
1255 if (fMatch)
1256 {
1257 if (!ppszValue)
1258 rc = VINF_SUCCESS;
1259 else
1260 {
1261 char *pszValue;
1262 rc = RTStrAllocEx(&pszValue, cchValue + 1);
1263 if (RT_SUCCESS(rc))
1264 {
1265 rc = ScmStreamRead(&Stream, pszValue, cchValue);
1266 if (RT_SUCCESS(rc))
1267 *ppszValue = pszValue;
1268 else
1269 RTStrFree(pszValue);
1270 }
1271 }
1272 break;
1273 }
1274
1275 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
1276 break;
1277 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
1278 break;
1279 }
1280
1281 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
1282 {
1283 rc = ScmStreamGetStatus(&Stream);
1284 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
1285 }
1286 ScmStreamDelete(&Stream);
1287 }
1288 }
1289
1290 if (rc == VERR_FILE_NOT_FOUND)
1291 rc = VERR_NOT_FOUND;
1292 }
1293 else
1294 {
1295 const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL };
1296 char *pszValue;
1297 rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue);
1298 if (RT_SUCCESS(rc))
1299 {
1300 if (pszValue && *pszValue)
1301 {
1302 if (ppszValue)
1303 {
1304 *ppszValue = pszValue;
1305 pszValue = NULL;
1306 }
1307 }
1308 else
1309 rc = VERR_NOT_FOUND;
1310 RTStrFree(pszValue);
1311 }
1312 }
1313 return rc;
1314}
1315
1316
1317/**
1318 * Schedules the setting of a property.
1319 *
1320 * @returns IPRT status code.
1321 * @retval VERR_INVALID_STATE if not a SVN WC file.
1322 * @param pState The rewrite state to work on.
1323 * @param pszName The name of the property to set.
1324 * @param pszValue The value. NULL means deleting it.
1325 */
1326int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
1327{
1328 /*
1329 * Update any existing entry first.
1330 */
1331 size_t i = pState->cSvnPropChanges;
1332 while (i-- > 0)
1333 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
1334 {
1335 if (!pszValue)
1336 {
1337 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1338 pState->paSvnPropChanges[i].pszValue = NULL;
1339 }
1340 else
1341 {
1342 char *pszCopy;
1343 int rc = RTStrDupEx(&pszCopy, pszValue);
1344 if (RT_FAILURE(rc))
1345 return rc;
1346 pState->paSvnPropChanges[i].pszValue = pszCopy;
1347 }
1348 return VINF_SUCCESS;
1349 }
1350
1351 /*
1352 * Insert a new entry.
1353 */
1354 i = pState->cSvnPropChanges;
1355 if ((i % 32) == 0)
1356 {
1357 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
1358 if (!pvNew)
1359 return VERR_NO_MEMORY;
1360 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
1361 }
1362
1363 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
1364 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
1365 if ( pState->paSvnPropChanges[i].pszName
1366 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
1367 pState->cSvnPropChanges = i + 1;
1368 else
1369 {
1370 RTStrFree(pState->paSvnPropChanges[i].pszName);
1371 pState->paSvnPropChanges[i].pszName = NULL;
1372 RTStrFree(pState->paSvnPropChanges[i].pszValue);
1373 pState->paSvnPropChanges[i].pszValue = NULL;
1374 return VERR_NO_MEMORY;
1375 }
1376 return VINF_SUCCESS;
1377}
1378
1379
1380/**
1381 * Schedules a property deletion.
1382 *
1383 * @returns IPRT status code.
1384 * @param pState The rewrite state to work on.
1385 * @param pszName The name of the property to delete.
1386 */
1387int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
1388{
1389 return ScmSvnSetProperty(pState, pszName, NULL);
1390}
1391
1392
1393/**
1394 * Applies any SVN property changes to the work copy of the file.
1395 *
1396 * @returns IPRT status code.
1397 * @param pState The rewrite state which SVN property changes
1398 * should be applied.
1399 */
1400int ScmSvnDisplayChanges(PSCMRWSTATE pState)
1401{
1402 size_t i = pState->cSvnPropChanges;
1403 while (i-- > 0)
1404 {
1405 const char *pszName = pState->paSvnPropChanges[i].pszName;
1406 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
1407 if (pszValue)
1408 ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
1409 else
1410 ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename);
1411 }
1412
1413 return VINF_SUCCESS;
1414}
1415
1416/**
1417 * Applies any SVN property changes to the work copy of the file.
1418 *
1419 * @returns IPRT status code.
1420 * @param pState The rewrite state which SVN property changes
1421 * should be applied.
1422 */
1423int ScmSvnApplyChanges(PSCMRWSTATE pState)
1424{
1425 scmSvnFindSvnBinary(pState);
1426
1427#ifdef SCM_WITH_LATER
1428 if (0)
1429 {
1430 return ...;
1431 }
1432
1433 /* Fallback: */
1434#endif
1435
1436 /*
1437 * Iterate thru the changes and apply them by starting the svn client.
1438 */
1439 for (size_t i = 0; i < pState->cSvnPropChanges; i++)
1440 {
1441 const char *apszArgv[6];
1442 apszArgv[0] = g_szSvnPath;
1443 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel";
1444 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
1445 int iArg = 3;
1446 if (pState->paSvnPropChanges[i].pszValue)
1447 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
1448 apszArgv[iArg++] = pState->pszFilename;
1449 apszArgv[iArg++] = NULL;
1450
1451 int rc = scmSvnRun(pState, apszArgv, false);
1452 if (RT_FAILURE(rc))
1453 return rc;
1454 }
1455
1456 return VINF_SUCCESS;
1457}
1458
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