VirtualBox

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

Last change on this file since 69433 was 69433, checked in by vboxsync, 7 years ago

scm: Reuse the subversion client context and pool, the former is very expensive to create.

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