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