1 /*
2 * rsvndump - remote svn repository dump
3 * Copyright (C) 2008-2012 Jonas Gehring
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 *
19 * file: path_repo.c
20 * desc: Versioned storage for file paths
21 */
22
23
24 #include <assert.h>
25
26 #include <apr_tables.h>
27
28 #include <svn_ra.h>
29
30 #include "main.h"
31
32 #include "delta.h"
33 #include "logger.h"
34 #include "mukv.h"
35 #include "utils.h"
36
37 #include "critbit89/critbit.h"
38 #ifdef USE_SNAPPY
39 #include "snappy-c/snappy.h"
40 #endif
41
42 #include "path_repo.h"
43
44
45 #define SNAPSHOT_INTERVAL (1<<10) /* Interval for full-tree snapshots */
46 #define CACHE_SIZE 4 /* Number of cached full trees */
47
48
49 /*---------------------------------------------------------------------------*/
50 /* Local data structures */
51 /*---------------------------------------------------------------------------*/
52
53
54 typedef struct {
55 svn_revnum_t revision;
56 cb_tree_t tree;
57 } pr_cache_entry_t;
58
59
60 typedef struct {
61 char action;
62 char *path;
63 } pr_delta_entry_t;
64
65
66 struct path_repo_t {
67 apr_pool_t *pool;
68 mukv_t *db;
69
70 apr_pool_t *delta_pool;
71 apr_array_header_t *delta;
72 int delta_len;
73 cb_tree_t tree;
74 svn_revnum_t head;
75
76 apr_array_header_t *cache; /* FIFO cache */
77 int cache_index;
78
79 #ifdef USE_SNAPPY
80 struct snappy_env snappy_env;
81 #endif
82
83 #ifdef DEBUG
84 size_t delta_bytes;
85 size_t delta_bytes_raw;
86 int cache_hits;
87 int cache_misses;
88 apr_time_t recon_time;
89 apr_time_t store_time;
90 #endif
91 };
92
93
94 /*---------------------------------------------------------------------------*/
95 /* Local functions */
96 /*---------------------------------------------------------------------------*/
97
98
99 /* Clears remaining memory of the path repo */
pr_cleanup(void * data)100 static apr_status_t pr_cleanup(void *data)
101 {
102 path_repo_t *repo = data;
103 int i;
104
105 #ifdef DEBUG
106 L1("path_repo: snapshot interval: %d\n", SNAPSHOT_INTERVAL);
107 L1("path_repo: cache size: %d\n", CACHE_SIZE);
108 L1("path_repo: stored deltas: %d kB\n", repo->delta_bytes / 1024);
109 L1("path_repo: stored deltas (raw): %d kB\n", repo->delta_bytes_raw / 1024);
110 L1("path_repo: total recon time: %ld ms\n", apr_time_msec(repo->recon_time));
111 L1("path_repo: total store time: %ld ms\n", apr_time_msec(repo->store_time));
112 L1("path_repo: cache miss rate: %.2f%% (%d of %d)\n", 100.0f*repo->cache_misses / (repo->cache_hits+repo->cache_misses), repo->cache_misses, (repo->cache_hits+repo->cache_misses));
113 #endif
114
115 cb_tree_clear(&repo->tree);
116 for (i = 0; i < repo->cache->nelts; i++) {
117 if (APR_ARRAY_IDX(repo->cache, i, pr_cache_entry_t).tree.root) {
118 cb_tree_clear(&APR_ARRAY_IDX(repo->cache, i, pr_cache_entry_t).tree);
119 }
120 }
121
122 mukv_close(repo->db);
123
124 #ifdef USE_SNAPPY
125 snappy_free_env(&repo->snappy_env);
126 #endif
127 return APR_SUCCESS;
128 }
129
130
131 /* Initializes the revision cache */
pr_cache_init(path_repo_t * repo,int size)132 static void pr_cache_init(path_repo_t *repo, int size)
133 {
134 int i;
135 repo->cache = apr_array_make(repo->pool, size, sizeof(pr_cache_entry_t));
136 for (i = 0; i < size; i++) {
137 APR_ARRAY_PUSH(repo->cache, pr_cache_entry_t).revision = -1;
138 APR_ARRAY_IDX(repo->cache, i, pr_cache_entry_t).tree = cb_tree_make();
139 }
140 repo->cache_index = 0;
141 }
142
143
144 /* Callback for pr_tree_to_array() */
145 struct pr_ttoa_data {
146 apr_array_header_t *arr;
147 size_t path_len;
148 apr_pool_t *pool;
149 };
pr_tree_to_array_cb(const char * elem,void * arg)150 static int pr_tree_to_array_cb(const char *elem, void *arg) {
151 struct pr_ttoa_data *data = arg;
152 if (data->path_len == 0 || elem[data->path_len] == 0 || elem[data->path_len] == '/') {
153 APR_ARRAY_PUSH(data->arr, const char *) = apr_pstrdup(data->pool, elem);
154 }
155 return 0;
156 }
157
158 /* Returns all children of path in the given tree as an array */
pr_tree_to_array(cb_tree_t * tree,const char * path,apr_pool_t * pool)159 static apr_array_header_t *pr_tree_to_array(cb_tree_t *tree, const char *path, apr_pool_t *pool)
160 {
161 struct pr_ttoa_data data;
162 data.arr = apr_array_make(pool, 0, sizeof(char *));
163 data.pool = pool;
164 data.path_len = strlen(path);
165 if (cb_tree_walk_prefixed(tree, path, pr_tree_to_array_cb, &data) != 0) {
166 return NULL;
167 }
168 return data.arr;
169 }
170
171
172 /* Encodes a whole tree to a series of add operations */
pr_encode(cb_tree_t * tree,char ** data,size_t * len,apr_pool_t * pool)173 static int pr_encode(cb_tree_t *tree, char **data, size_t *len, apr_pool_t *pool)
174 {
175 int i;
176 char *dptr;
177 apr_array_header_t *arr = pr_tree_to_array(tree, "", pool);
178 if (arr == NULL) {
179 return -1;
180 }
181
182 /* Compute length */
183 *len = 0;
184 for (i = 0; i < arr->nelts; i++) {
185 *len += 2 + strlen(APR_ARRAY_IDX(arr, i, char *));
186 }
187
188 /* Encode */
189 *data = apr_palloc(pool, *len);
190 dptr = *data;
191 for (i = 0; i < arr->nelts; i++) {
192 *dptr++ = '+';
193 strcpy(dptr, APR_ARRAY_IDX(arr, i, char *));
194 dptr += strlen(APR_ARRAY_IDX(arr, i, char *));
195 *dptr++ = '\0';
196 }
197 return 0;
198 }
199
200
201 /* Applies a serialized tree delta to a tree */
pr_delta_apply(cb_tree_t * tree,const char * data,int len,apr_pool_t * pool)202 static int pr_delta_apply(cb_tree_t *tree, const char *data, int len, apr_pool_t *pool)
203 {
204 const char *dptr = data;
205 while (dptr - data < len) {
206 if (*dptr == '+') {
207 cb_tree_insert(tree, dptr+1);
208 } else {
209 cb_tree_delete(tree, dptr+1);
210 }
211 dptr += 2 + strlen(dptr+1);
212 }
213 return 0;
214 }
215
216
217 /* Reconstructs a tree for the given revision */
pr_reconstruct(path_repo_t * repo,cb_tree_t * tree,svn_revnum_t revision,apr_pool_t * pool)218 static int pr_reconstruct(path_repo_t *repo, cb_tree_t *tree, svn_revnum_t revision, apr_pool_t *pool)
219 {
220 mdatum_t key, val;
221 svn_revnum_t r;
222 char *dptr;
223 size_t dsize;
224 #ifdef DEBUG
225 apr_time_t start = apr_time_now();
226 #endif
227
228 /* Start at position of last snapshot and apply deltas */
229 r = (revision & ~(SNAPSHOT_INTERVAL-1));
230 while (r <= revision) {
231 key.dptr = apr_itoa(pool, r);
232 key.dsize = strlen(key.dptr);
233
234 if (mukv_exists(repo->db, key)) {
235 val = mukv_fetch(repo->db, key, pool);
236 if (val.dptr == NULL) {
237 fprintf(stderr, _("Error fetching tree delta for revision %ld\n"), r);
238 return -1;
239 }
240 #ifdef USE_SNAPPY
241 if (!snappy_uncompressed_length(val.dptr, val.dsize, &dsize)) {
242 return -1;
243 }
244 dptr = malloc(dsize);
245 if (snappy_uncompress(val.dptr, val.dsize, dptr) != 0) {
246 free(dptr);
247 return -1;
248 }
249 #else
250 dptr = val.dptr;
251 dsize = val.dsize;
252 #endif
253 if (pr_delta_apply(tree, dptr, dsize, pool) != 0) {
254 fprintf(stderr, _("Error applying tree delta for revision %ld\n"), r);
255 return -1;
256 }
257
258 #ifdef USE_SNAPPY
259 free(dptr);
260 #endif
261 }
262 ++r;
263 }
264
265 #ifdef DEBUG
266 repo->recon_time += (apr_time_now() - start);
267 #endif
268 return 0;
269 }
270
271
272 /* Returns a tree for the given revision */
pr_tree(path_repo_t * repo,svn_revnum_t revision,apr_pool_t * pool)273 static cb_tree_t *pr_tree(path_repo_t *repo, svn_revnum_t revision, apr_pool_t *pool)
274 {
275 cb_tree_t *tree;
276 int i;
277
278 /* Check if tree is cached */
279 for (i = 0; i < repo->cache->nelts; i++) {
280 if (APR_ARRAY_IDX(repo->cache, i, pr_cache_entry_t).revision == revision) {
281 tree = &APR_ARRAY_IDX(repo->cache, i, pr_cache_entry_t).tree;
282 #ifdef DEBUG
283 repo->cache_hits++;
284 #endif
285 break;
286 }
287 }
288
289 /* Reconstruct tree if needed */
290 if (i >= repo->cache->nelts) {
291 #ifdef DEBUG
292 repo->cache_misses++;
293 #endif
294 tree = &APR_ARRAY_IDX(repo->cache, repo->cache_index, pr_cache_entry_t).tree;
295 if (tree->root != NULL) {
296 cb_tree_clear(tree);
297 }
298 if (pr_reconstruct(repo, tree, revision, pool) != 0) {
299 return NULL;
300 }
301
302 APR_ARRAY_IDX(repo->cache, repo->cache_index, pr_cache_entry_t).revision = revision;
303 if (++repo->cache_index >= repo->cache->nelts) {
304 repo->cache_index = 0;
305 }
306 }
307
308 return tree;
309 }
310
311
312 /* Fetches paths from the repository and stores them into the given array */
pr_fetch_paths_rec(apr_array_header_t * paths,const char * path,svn_revnum_t rev,session_t * session,apr_pool_t * pool)313 static int pr_fetch_paths_rec(apr_array_header_t *paths, const char *path, svn_revnum_t rev, session_t *session, apr_pool_t *pool)
314 {
315 svn_error_t *err;
316 apr_hash_t *dirents;
317 apr_hash_index_t *hi;
318
319 if ((err = svn_ra_get_dir2(session->ra, &dirents, NULL, NULL, path, rev, SVN_DIRENT_KIND, pool))) {
320 utils_handle_error(err, stderr, FALSE, "ERROR: ");
321 svn_error_clear(err);
322 return -1;
323 }
324
325 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) {
326 const char *entry;
327 char *subpath;
328 svn_dirent_t *dirent;
329 apr_hash_this(hi, (const void **)&entry, NULL, (void **)&dirent);
330
331 /* Add the full path of the entry */
332 if (strlen(path) > 0) {
333 subpath = apr_psprintf(pool, "%s/%s", path, entry);
334 } else {
335 subpath = apr_pstrdup(pool, entry);
336 }
337
338 if (dirent->kind == svn_node_file) {
339 APR_ARRAY_PUSH(paths, char *) = subpath;
340 } else if (dirent->kind == svn_node_dir) {
341 APR_ARRAY_PUSH(paths, char *) = subpath;
342 pr_fetch_paths_rec(paths, subpath, rev, session, pool);
343 }
344 }
345 return 0;
346 }
347
348 /* Fetches paths from the repository and stores them into the given array */
pr_fetch_paths(apr_array_header_t * paths,const char * path,svn_revnum_t rev,session_t * session,apr_pool_t * pool)349 static int pr_fetch_paths(apr_array_header_t *paths, const char *path, svn_revnum_t rev, session_t *session, apr_pool_t *pool)
350 {
351 svn_error_t *err;
352 svn_dirent_t *dirent;
353
354 /* Check node type */
355 if ((err = svn_ra_stat(session->ra, path, rev, &dirent, pool))) {
356 utils_handle_error(err, stderr, FALSE, "ERROR: ");
357 svn_error_clear(err);
358 return -1;
359 }
360
361 if (dirent == NULL) {
362 DEBUG_MSG("pr_fetch_paths(): Non-existent: %s@%ld\n", path, rev);
363 return 0;
364 }
365
366 APR_ARRAY_PUSH(paths, char *) = apr_pstrdup(pool, path);
367 if (dirent->kind == svn_node_file) {
368 return 0;
369 }
370 return pr_fetch_paths_rec(paths, path, rev, session, pool);
371 }
372
373
374 /*---------------------------------------------------------------------------*/
375 /* Global functions */
376 /*---------------------------------------------------------------------------*/
377
378
379 /* Creates a new path repository in the given directory */
path_repo_create(const char * tmpdir,apr_pool_t * pool)380 path_repo_t *path_repo_create(const char *tmpdir, apr_pool_t *pool)
381 {
382 apr_pool_t *subpool = svn_pool_create(pool);
383 path_repo_t *repo = apr_pcalloc(subpool, sizeof(path_repo_t));
384 const char *db_path;
385
386 repo->pool = subpool;
387 repo->delta_pool = svn_pool_create(repo->pool);
388
389 repo->tree = cb_tree_make();
390 repo->delta = apr_array_make(repo->pool, 1, sizeof(pr_delta_entry_t));
391 pr_cache_init(repo, CACHE_SIZE);
392
393 /* Open database */
394 db_path = apr_psprintf(pool, "%s/paths.db", tmpdir);
395 repo->db = mukv_open(db_path, repo->pool);
396 if (repo->db == NULL) {
397 fprintf(stderr, _("Error creating path database (%s)\n"), strerror(errno));
398 return NULL;
399 }
400
401 #ifdef USE_SNAPPY
402 if (snappy_init_env(&repo->snappy_env) != 0) {
403 fprintf(stderr, _("Error initializing snappy compressor\n"));
404 return NULL;
405 }
406 #endif
407
408 apr_pool_cleanup_register(repo->pool, repo, pr_cleanup, NULL);
409 return repo;
410 }
411
412
413 /* Schedules the given path for addition */
path_repo_add(path_repo_t * repo,const char * path,apr_pool_t * pool)414 int path_repo_add(path_repo_t *repo, const char *path, apr_pool_t *pool)
415 {
416 pr_delta_entry_t *e = &APR_ARRAY_PUSH(repo->delta, pr_delta_entry_t);
417 e->action = '+';
418 e->path = apr_pstrdup(repo->delta_pool, path);
419 repo->delta_len += (2 + strlen(path));
420
421 if (cb_tree_insert(&repo->tree, e->path) != 0) {
422 return -1;
423 }
424
425 (void)pool; /* Prevent compiler warnings */
426 return 0;
427 }
428
429
430 /* Schedules the given path for deletion */
path_repo_delete(path_repo_t * repo,const char * path,apr_pool_t * pool)431 int path_repo_delete(path_repo_t *repo, const char *path, apr_pool_t *pool)
432 {
433 apr_array_header_t *paths = pr_tree_to_array(&repo->tree, path, pool);
434 int i;
435
436 for (i = 0; i < paths->nelts; i++) {
437 char *p = APR_ARRAY_IDX(paths, i, char *);
438 pr_delta_entry_t *e = &APR_ARRAY_PUSH(repo->delta, pr_delta_entry_t);
439 e->action = '-';
440 e->path = apr_pstrdup(repo->delta_pool, p);
441 repo->delta_len += (2 + strlen(p));
442
443 cb_tree_delete(&repo->tree, e->path);
444 }
445 return 0;
446 }
447
448
449 /* Commits all scheduled actions, using the given revision number */
path_repo_commit(path_repo_t * repo,svn_revnum_t revision,apr_pool_t * pool)450 int path_repo_commit(path_repo_t *repo, svn_revnum_t revision, apr_pool_t *pool)
451 {
452 mdatum_t key, val;
453 int i;
454 char *dptr = NULL;
455 #ifdef USE_SNAPPY
456 size_t dsize;
457 #endif
458 #ifdef DEBUG
459 apr_time_t start = apr_time_now();
460 #endif
461 int snapshot = (revision > 0 && (revision % SNAPSHOT_INTERVAL == 0));
462
463 /* Skip empty revisions if there's no snapshot pending */
464 if (repo->delta_len <= 0 && !snapshot) {
465 repo->head = revision;
466 return 0;
467 }
468
469 /* Encode data if necessary */
470 if (!snapshot) {
471 val.dptr = apr_palloc(pool, repo->delta_len);
472 val.dsize = repo->delta_len;
473 dptr = val.dptr;
474
475 for (i = 0; i < repo->delta->nelts; i++) {
476 pr_delta_entry_t *e = &APR_ARRAY_IDX(repo->delta, i, pr_delta_entry_t);
477
478 *dptr++ = e->action;
479 strcpy(dptr, e->path);
480 dptr += strlen(e->path);
481 *dptr++ = '\0';
482 }
483 } else {
484 if (pr_encode(&repo->tree, &val.dptr, &val.dsize, pool) != 0) {
485 fprintf(stderr, _("Error encoding tree data for snapshot\n"));
486 return -1;
487 }
488 }
489
490 #ifdef DEBUG
491 repo->delta_bytes_raw += val.dsize;
492 #endif
493
494 #ifdef USE_SNAPPY
495 dptr = apr_palloc(pool, snappy_max_compressed_length(val.dsize));
496 if (snappy_compress(&repo->snappy_env, val.dptr, val.dsize, dptr, &dsize) != 0) {
497 fprintf(stderr, _("Error compressing tree data for revision %ld\n"), revision);
498 return -1;
499 }
500 val.dptr = dptr;
501 val.dsize = dsize;
502 #endif
503
504 #ifdef DEBUG
505 repo->delta_bytes += val.dsize;
506 #endif
507
508 key.dptr = apr_itoa(pool, revision);
509 key.dsize = strlen(key.dptr);
510 if (mukv_store(repo->db, key, val) != 0) {
511 fprintf(stderr, _("Error storing paths for revision %ld\n"), revision);
512 return -1;
513 }
514
515 repo->head = revision;
516 repo->delta_len = 0;
517 apr_array_clear(repo->delta);
518 svn_pool_clear(repo->delta_pool);
519 #ifdef DEBUG
520 repo->store_time += (apr_time_now() - start);
521 #endif
522 return 0;
523 }
524
525
526 /* Discards all scheduled actions */
path_repo_discard(path_repo_t * repo,apr_pool_t * pool)527 int path_repo_discard(path_repo_t *repo, apr_pool_t *pool)
528 {
529 repo->delta_len = 0;
530 apr_array_clear(repo->delta);
531 svn_pool_clear(repo->delta_pool);
532
533 /* Revert to previous head */
534 cb_tree_clear(&repo->tree);
535 return pr_reconstruct(repo, &repo->tree, repo->head, pool);
536 }
537
538
539 /* Commits a SVN log entry, using the given revision number */
path_repo_commit_log(path_repo_t * repo,session_t * session,dump_options_t * opts,log_revision_t * log,svn_revnum_t revision,apr_array_header_t * logs,apr_pool_t * pool)540 int path_repo_commit_log(path_repo_t *repo, session_t *session, dump_options_t *opts, log_revision_t *log, svn_revnum_t revision, apr_array_header_t *logs, apr_pool_t *pool)
541 {
542 apr_hash_index_t *hi;
543 apr_array_header_t *paths;
544 int i, j;
545
546 if (log->changed_paths == NULL) {
547 /* Commit empty revision */
548 return path_repo_commit(repo, revision, pool);
549 }
550
551 /* Sort changed paths */
552 /* TODO: More advanced sorting necessary? */
553 paths = apr_array_make(pool, apr_hash_count(log->changed_paths), sizeof(const char *));
554 for (hi = apr_hash_first(pool, log->changed_paths); hi; hi = apr_hash_next(hi)) {
555 const char *path;
556 apr_hash_this(hi, (const void **)&path, NULL, NULL);
557 APR_ARRAY_PUSH(paths, const char *) = path;
558 }
559 utils_sort(paths);
560
561 /* Fill current delta */
562 for (i = 0; i < paths->nelts; i++) {
563 const char *path = APR_ARRAY_IDX(paths, i, const char *);
564 svn_log_changed_path_t *info = apr_hash_get(log->changed_paths, path, APR_HASH_KEY_STRING);
565
566 if (info->copyfrom_path == NULL) {
567 DEBUG_MSG("path_repo_commit(%ld): %c %s\n", revision, info->action, path);
568 } else {
569 DEBUG_MSG("path_repo_commit(%ld): %c %s (from %s@%ld)\n", revision, info->action, path, info->copyfrom_path, info->copyfrom_rev);
570 }
571
572 if (info->action == 'D' || info->action == 'R') {
573 path_repo_delete(repo, path, pool);
574 }
575 if (info->action != 'A' && info->action != 'R') {
576 continue;
577 }
578
579 /* info->action == 'A' || info->action == 'R' */
580 if (info->copyfrom_path == NULL) {
581 path_repo_add(repo, path, pool);
582 } else {
583 apr_array_header_t *cpaths;
584 const char *copyfrom_path = delta_get_local_copyfrom_path(session->prefix, info->copyfrom_path);
585
586 if (copyfrom_path == NULL) {
587 cpaths = apr_array_make(pool, 1, sizeof(char *));
588 if (pr_fetch_paths(cpaths, path, log->revision, session, pool) != 0) {
589 fprintf(stderr, _("Error fetching tree for revision %ld\n"), log->revision);
590 return -1;
591 }
592
593 for (j = 0; j < cpaths->nelts; j++) {
594 path_repo_add(repo, APR_ARRAY_IDX(cpaths, j, char *), pool);
595 }
596 } else {
597 svn_revnum_t copyfrom_rev = delta_get_local_copyfrom_rev(info->copyfrom_rev, opts, logs, revision);
598 cb_tree_t *tree = pr_tree(repo, copyfrom_rev, pool);
599 if (tree == NULL) {
600 return -1;
601 }
602
603 cpaths = pr_tree_to_array(tree, copyfrom_path, pool);
604 assert(cpaths->nelts > 0);
605
606 if (cpaths->nelts == 1) {
607 /* Single file copied */
608 path_repo_add(repo, path, pool);
609 } else {
610 unsigned int copyfrom_path_len = strlen(copyfrom_path);
611 for (j = 0; j < cpaths->nelts; j++) {
612 const char *relpath = APR_ARRAY_IDX(cpaths, j, char *);
613 assert(strlen(relpath) >= copyfrom_path_len);
614 relpath = apr_psprintf(pool, "%s%s", path, relpath + copyfrom_path_len);
615 path_repo_add(repo, relpath, pool);
616 }
617 }
618 }
619 }
620 }
621
622 /* Commit */
623 return path_repo_commit(repo, revision, pool);
624 }
625
626
627 /* Checks if a path exists at a given revision */
path_repo_exists(path_repo_t * repo,const char * path,svn_revnum_t revision,apr_pool_t * pool)628 extern signed char path_repo_exists(path_repo_t *repo, const char *path, svn_revnum_t revision, apr_pool_t *pool)
629 {
630 cb_tree_t *tree;
631 if (revision < 0) {
632 return 0;
633 }
634
635 tree = pr_tree(repo, revision, pool);
636 if (tree == NULL) {
637 return -1;
638 }
639 if (cb_tree_contains(tree, path)) {
640 return 1;
641 }
642 return 0;
643 }
644
645
646 /* Checks the parent relation of two paths at a given revision */
path_repo_check_parent(path_repo_t * repo,const char * parent,const char * child,svn_revnum_t revision,apr_pool_t * pool)647 signed char path_repo_check_parent(path_repo_t *repo, const char *parent, const char *child, svn_revnum_t revision, apr_pool_t *pool)
648 {
649 char *path;
650 cb_tree_t *tree;
651
652 if (revision < 0) {
653 return 0;
654 }
655
656 tree = pr_tree(repo, revision, pool);
657 if (tree == NULL) {
658 return -1;
659 }
660 path = apr_psprintf(pool, "%s/%s", parent, child);
661 if (cb_tree_contains(tree, path)) {
662 return 1;
663 }
664 return 0;
665 }
666
667 #ifdef DEBUG
668
669 /* Verifies a given revision */
path_repo_test(path_repo_t * repo,session_t * session,svn_revnum_t revision,svn_revnum_t svn_rev,apr_pool_t * pool)670 int path_repo_test(path_repo_t *repo, session_t *session, svn_revnum_t revision, svn_revnum_t svn_rev, apr_pool_t *pool)
671 {
672 apr_array_header_t *paths_recon;
673 apr_array_header_t *paths_orig;
674 cb_tree_t tree = cb_tree_make();
675 int i, ret = 0;
676
677 /* Retrieve reconstructed tree */
678 if (pr_reconstruct(repo, &tree, revision, pool) != 0) {
679 fprintf(stderr, _("Error reconstructing tree for revision %ld\n"), revision);
680 return 1;
681 }
682 paths_recon = pr_tree_to_array(&tree, "", pool);
683
684 /* Retrieve actual tree -- assume the session is rooted at a directory */
685 paths_orig = apr_array_make(pool, 0, sizeof(char *));
686 pr_fetch_paths(paths_orig, "", svn_rev, session, pool);
687 utils_sort(paths_orig);
688
689 /* Skip empty root element from original tree (HACK!) */
690 if (paths_orig->nelts > 0 && strlen(APR_ARRAY_IDX(paths_orig, 0, char *)) == 0) {
691 paths_orig->elts += sizeof(char *);
692 paths_orig->nelts--;
693 }
694
695 /* Compare trees */
696 if (paths_recon->nelts != paths_orig->nelts) {
697 fprintf(stderr, "r%ld: #recon = %d != %d = #orig\n", revision, paths_recon->nelts, paths_orig->nelts);
698 ret = 1;
699 }
700 for (i = 0; i < paths_recon->nelts; i++) {
701 char *p = APR_ARRAY_IDX(paths_recon, i, char *);
702 if (utils_search(p, paths_orig) == NULL) {
703 fprintf(stderr, "r%ld: in recon, not in orig: %s\n", revision, p);
704 ret = 1;
705 }
706 }
707 for (i = 0; i < paths_orig->nelts; i++) {
708 char *p = APR_ARRAY_IDX(paths_orig, i, char *);
709 if (utils_search(p, paths_recon) == NULL) {
710 fprintf(stderr, "r%ld: in orig, not in recon: %s\n", revision, p);
711 ret = 1;
712 }
713 }
714
715 cb_tree_clear(&tree);
716 return ret;
717 }
718
719
720 /* Verifies all revisions stored in the path repository */
path_repo_test_all(path_repo_t * repo,session_t * session,apr_pool_t * pool)721 int path_repo_test_all(path_repo_t *repo, session_t *session, apr_pool_t *pool)
722 {
723 svn_revnum_t rev = 0;
724 apr_pool_t *revpool = svn_pool_create(pool);
725 int ret = 0;
726
727 L0("Checking path_repo until revision %ld...\n", repo->head);
728 while (ret == 0 && ++rev < repo->head) {
729 mdatum_t key;
730
731 /* Skip all revisions that haven't been committed */
732 key.dptr = apr_itoa(pool, rev);
733 key.dsize = strlen(key.dptr);
734 if (!mukv_exists(repo->db, key)) {
735 continue;
736 }
737
738 ret = path_repo_test(repo, session, rev, rev, revpool);
739 svn_pool_clear(revpool);
740 }
741 return ret;
742 }
743
744 #endif /* DEBUG */
745