* subversion/mod_dav_svn/reports/replay.c
[svn.git] / subversion / libsvn_fs_util / mergeinfo-sqlite-index.c
blob3310b0e3be9ce7c1cc31a1b168acfdbc4ee5464f
1 /* mergeinfo-sqlite-index.c
3 * ====================================================================
4 * Copyright (c) 2006-2007 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <assert.h>
24 #include <apr.h>
25 #include <apr_general.h>
26 #include <apr_pools.h>
28 #include <sqlite3.h>
30 #include "svn_fs.h"
31 #include "svn_path.h"
32 #include "svn_mergeinfo.h"
33 #include "svn_pools.h"
35 #include "private/svn_dep_compat.h"
36 #include "private/svn_fs_sqlite.h"
37 #include "private/svn_fs_mergeinfo.h"
38 #include "../libsvn_fs/fs-loader.h"
39 #include "svn_private_config.h"
41 #include "sqlite-util.h"
44 * A general warning about the mergeinfo tables:
46 * The sqlite transaction is committed (immediately) before the actual
47 * FS transaction is committed. Thus, any query against any mergeinfo
48 * table MUST contain a guard on the revision column guaranteeing that
49 * the returned rows have a revision value no greater than some
50 * known-committed revision number!
54 /* This is a macro implementation of svn_fs_revision_root_revision(), which
55 we cannot call from here, because it would create a circular dependency. */
56 #define REV_ROOT_REV(root) \
57 ((root)->is_txn_root ? SVN_INVALID_REVNUM : (root)->rev)
59 /* We want to cache that we saw no mergeinfo for a path as well,
60 so we use a -1 converted to a pointer to represent this. */
61 #define NEGATIVE_CACHE_RESULT ((void *)(-1))
63 /* A flow-control helper macro for sending processing to the 'cleanup'
64 label when the local variable 'err' is not SVN_NO_ERROR. */
65 #define MAYBE_CLEANUP if (err) goto cleanup
68 static svn_error_t *
69 get_mergeinfo_for_path(sqlite3 *db,
70 const char *path,
71 svn_revnum_t rev,
72 apr_hash_t *result,
73 apr_hash_t *cache,
74 svn_mergeinfo_inheritance_t inherit,
75 apr_pool_t *pool);
77 /* Represents "no mergeinfo". */
78 static svn_merge_range_t no_mergeinfo = { SVN_INVALID_REVNUM,
79 SVN_INVALID_REVNUM,
80 TRUE };
82 /* Insert the necessary indexing data into the DB for all the merges
83 on PATH as of NEW_REV, which is provided (unparsed) in
84 MERGEINFO_STR. Use POOL for temporary allocations.*/
85 static svn_error_t *
86 index_path_mergeinfo(svn_revnum_t new_rev,
87 sqlite3 *db,
88 const char *path,
89 svn_string_t *mergeinfo_str,
90 apr_pool_t *pool)
92 apr_hash_t *mergeinfo;
93 apr_hash_index_t *hi;
94 svn_fs__sqlite_stmt_t *stmt;
95 svn_boolean_t remove_mergeinfo = FALSE;
97 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data, pool));
99 if (apr_hash_count(mergeinfo) == 0)
101 /* All mergeinfo has been removed from PATH (or explicitly set
102 to "none", if there previously was no mergeinfo). Find all
103 previous mergeinfo, and (further below) insert dummy records
104 representing "no mergeinfo" for all its previous merge
105 sources of PATH.
107 Even though POOL is for temporary allocations, invocation of
108 get_mergeinfo_for_path() necessitates its own sub-pool. */
109 apr_pool_t *subpool = svn_pool_create(pool);
110 apr_hash_t *cache = apr_hash_make(subpool);
111 remove_mergeinfo = TRUE;
112 SVN_ERR(get_mergeinfo_for_path(db, path, new_rev - 1, NULL, cache,
113 svn_mergeinfo_inherited, subpool));
114 mergeinfo = apr_hash_get(cache, path, APR_HASH_KEY_STRING);
115 if (mergeinfo == NEGATIVE_CACHE_RESULT)
116 mergeinfo = NULL;
117 if (mergeinfo)
118 mergeinfo = svn_mergeinfo_dup(mergeinfo, pool);
120 svn_pool_destroy(subpool);
122 if (mergeinfo == NULL)
123 /* There was previously no mergeinfo, inherited or explicit,
124 for PATH. */
125 return SVN_NO_ERROR;
128 for (hi = apr_hash_first(NULL, mergeinfo);
129 hi != NULL;
130 hi = apr_hash_next(hi))
132 const char *from;
133 apr_array_header_t *rangelist;
134 const void *key;
135 void *val;
137 apr_hash_this(hi, &key, NULL, &val);
139 from = key;
140 rangelist = val;
141 if (from && rangelist)
143 int i;
144 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db,
145 "INSERT INTO mergeinfo (revision, "
146 "mergedfrom, mergedto, mergedrevstart, "
147 "mergedrevend, inheritable) VALUES (?, "
148 "?, ?, ?, ?, ?);", pool));
149 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 1, new_rev));
150 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 2, from));
151 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 3, path));
153 if (remove_mergeinfo)
155 /* Explicitly set "no mergeinfo" for PATH, which may've
156 previously had only inherited mergeinfo. */
157 #if APR_VERSION_AT_LEAST(1, 3, 0)
158 apr_array_clear(rangelist);
159 #else
160 /* Use of an iterpool would be overkill here. */
161 rangelist = apr_array_make(pool, 1, sizeof(&no_mergeinfo));
162 #endif
163 APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = &no_mergeinfo;
166 for (i = 0; i < rangelist->nelts; i++)
168 const svn_merge_range_t *range =
169 APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
170 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 4, range->start));
171 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 5, range->end));
172 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 6, range->inheritable));
173 SVN_ERR(svn_fs__sqlite_step_done(stmt));
175 SVN_ERR(svn_fs__sqlite_reset(stmt));
177 SVN_ERR(svn_fs__sqlite_finalize(stmt));
181 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db,
182 "INSERT INTO mergeinfo_changed (revision, "
183 "path) VALUES (?, ?);", pool));
184 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 1, new_rev));
185 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 2, path));
187 SVN_ERR(svn_fs__sqlite_step_done(stmt));
189 SVN_ERR(svn_fs__sqlite_finalize(stmt));
191 return SVN_NO_ERROR;
195 /* Index the mergeinfo for each path in MERGEINFO_FOR_PATHS (a
196 mapping of const char * -> to svn_string_t *). */
197 static svn_error_t *
198 index_txn_mergeinfo(sqlite3 *db,
199 svn_revnum_t new_rev,
200 apr_hash_t *mergeinfo_for_paths,
201 apr_pool_t *pool)
203 apr_hash_index_t *hi;
205 for (hi = apr_hash_first(pool, mergeinfo_for_paths);
206 hi != NULL;
207 hi = apr_hash_next(hi))
209 const void *path;
210 void *mergeinfo;
212 apr_hash_this(hi, &path, NULL, &mergeinfo);
213 SVN_ERR(index_path_mergeinfo(new_rev, db, (const char *) path,
214 (svn_string_t *) mergeinfo, pool));
216 return SVN_NO_ERROR;
219 static svn_error_t *
220 table_has_any_rows_with_rev(svn_boolean_t *has_any,
221 sqlite3 *db,
222 const char *table,
223 svn_revnum_t rev,
224 apr_pool_t *pool)
226 /* Note that we can't use the bind API for table names. (And if
227 we're sprintfing once, we might as well plug in the revision
228 while we're at it; it's safe.) */
229 const char *selection = apr_psprintf(pool,
230 "SELECT 1 from %s WHERE "
231 "revision = %ld;",
232 table, rev);
233 svn_fs__sqlite_stmt_t *stmt;
235 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db, selection, pool));
236 SVN_ERR(svn_fs__sqlite_step(has_any, stmt));
237 SVN_ERR(svn_fs__sqlite_finalize(stmt));
239 return SVN_NO_ERROR;
242 /* Remove any mergeinfo already stored at NEW_REV from DB. (This will
243 exist if a previous transaction failed between sqlite
244 commit-transaction and svn commit-transaction time, say.) If
245 AVOID_NOOP_DELETE is true, only run the delete commands if there's
246 definitely data there to delete.
248 static svn_error_t *
249 clean_tables(sqlite3 *db,
250 svn_revnum_t new_rev,
251 svn_boolean_t avoid_noop_delete,
252 apr_pool_t *pool)
254 const char *deletestring;
256 if (avoid_noop_delete)
258 svn_boolean_t has_any;
259 SVN_ERR(table_has_any_rows_with_rev(&has_any, db, "mergeinfo",
260 new_rev, pool));
262 if (! has_any)
263 SVN_ERR(table_has_any_rows_with_rev(&has_any, db, "mergeinfo_changed",
264 new_rev, pool));
266 if (! has_any)
267 return SVN_NO_ERROR;
270 deletestring = apr_psprintf(pool,
271 "DELETE FROM mergeinfo_changed WHERE "
272 "revision = %ld;",
273 new_rev);
274 SVN_ERR(svn_fs__sqlite_exec(db, deletestring));
276 deletestring = apr_psprintf(pool,
277 "DELETE FROM mergeinfo WHERE revision = %ld;",
278 new_rev);
279 SVN_ERR(svn_fs__sqlite_exec(db, deletestring));
281 return SVN_NO_ERROR;
284 /* Clean the mergeinfo index for any previous failed commit with the
285 revision number as NEW_REV, and if the current transaction contains
286 mergeinfo, record it. */
287 svn_error_t *
288 svn_fs_mergeinfo__update_index(svn_fs_txn_t *txn, svn_revnum_t new_rev,
289 apr_hash_t *mergeinfo_for_paths,
290 apr_pool_t *pool)
292 svn_error_t *err;
293 sqlite3 *db;
294 apr_pool_t *subpool = svn_pool_create(pool);
296 SVN_ERR(svn_fs__sqlite_open(&db, txn->fs->path, subpool));
297 err = svn_fs__sqlite_exec(db, "BEGIN TRANSACTION;");
298 MAYBE_CLEANUP;
300 /* Clean up old data. (If we're going to write to the DB anyway,
301 there's no reason to do extra checks to avoid no-op DELETEs.) */
302 err = clean_tables(db,
303 new_rev,
304 (mergeinfo_for_paths == NULL),
305 subpool);
306 MAYBE_CLEANUP;
308 /* Record any mergeinfo from the current transaction. */
309 if (mergeinfo_for_paths)
311 err = index_txn_mergeinfo(db, new_rev, mergeinfo_for_paths, subpool);
312 MAYBE_CLEANUP;
315 /* This is moved here from FSFS's commit_txn, because we don't want to
316 * write the final current file if the sqlite commit fails.
317 * On the other hand, if we commit the transaction and end up failing
318 * the current file, we just end up with inaccessible data in the
319 * database, not a real problem. */
320 err = svn_fs__sqlite_exec(db, "COMMIT TRANSACTION;");
321 MAYBE_CLEANUP;
323 cleanup:
324 err = svn_fs__sqlite_close(db, err);
325 svn_pool_destroy(subpool);
326 return err;
329 /* Helper for get_mergeinfo_for_path() that retrieves mergeinfo for
330 PATH at the revision LASTMERGED_REV, returning it in the mergeinfo
331 hash *RESULT (with rangelist elements in ascending order). Perform
332 all allocations in POOL. */
333 static svn_error_t *
334 parse_mergeinfo_from_db(sqlite3 *db,
335 const char *path,
336 svn_revnum_t lastmerged_rev,
337 apr_hash_t **result,
338 apr_pool_t *pool)
340 svn_fs__sqlite_stmt_t *stmt;
341 svn_boolean_t got_row;
343 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db,
344 "SELECT mergedfrom, mergedrevstart, "
345 "mergedrevend, inheritable FROM mergeinfo "
346 "WHERE mergedto = ? AND revision = ? "
347 "ORDER BY mergedfrom, mergedrevstart;",
348 pool));
349 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 1, path));
350 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 2, lastmerged_rev));
351 SVN_ERR(svn_fs__sqlite_step(&got_row, stmt));
353 /* It is possible the mergeinfo changed because of a delete, and
354 that the mergeinfo is now gone. */
355 if (! got_row)
356 *result = NULL;
357 else
359 apr_array_header_t *pathranges;
360 const char *mergedfrom;
361 svn_revnum_t startrev;
362 svn_revnum_t endrev;
363 svn_boolean_t inheritable;
364 const char *lastmergedfrom = NULL;
366 *result = apr_hash_make(pool);
367 pathranges = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
369 while (got_row)
371 mergedfrom = svn_fs__sqlite_column_text(stmt, 0);
372 startrev = svn_fs__sqlite_column_revnum(stmt, 1);
373 endrev = svn_fs__sqlite_column_revnum(stmt, 2);
374 inheritable = svn_fs__sqlite_column_boolean(stmt, 3);
376 mergedfrom = apr_pstrdup(pool, mergedfrom);
377 if (lastmergedfrom && strcmp(mergedfrom, lastmergedfrom) != 0)
379 /* This iteration over the result set starts a group of
380 mergeinfo with a different merge source. */
381 apr_hash_set(*result, lastmergedfrom, APR_HASH_KEY_STRING,
382 pathranges);
383 pathranges = apr_array_make(pool, 1,
384 sizeof(svn_merge_range_t *));
387 /* Filter out invalid revision numbers, which are assumed to
388 represent dummy records indicating that a merge source
389 has no mergeinfo for PATH. */
390 if (SVN_IS_VALID_REVNUM(startrev) && SVN_IS_VALID_REVNUM(endrev))
392 svn_merge_range_t *range = apr_pcalloc(pool, sizeof(*range));
393 range->start = startrev;
394 range->end = endrev;
395 range->inheritable = inheritable;
396 APR_ARRAY_PUSH(pathranges, svn_merge_range_t *) = range;
399 SVN_ERR(svn_fs__sqlite_step(&got_row, stmt));
400 lastmergedfrom = mergedfrom;
403 apr_hash_set(*result, mergedfrom, APR_HASH_KEY_STRING, pathranges);
406 SVN_ERR(svn_fs__sqlite_finalize(stmt));
407 return SVN_NO_ERROR;
411 /* Helper for get_mergeinfo_for_path() that will append PATH_TO_APPEND
412 to each path that exists in the mergeinfo hash INPUT, and return a
413 new mergeinfo hash in *OUTPUT. Perform all allocations in POOL. */
414 static svn_error_t *
415 append_component_to_paths(apr_hash_t **output,
416 apr_hash_t *input,
417 const char *path_to_append,
418 apr_pool_t *pool)
420 apr_hash_index_t *hi;
421 *output = apr_hash_make(pool);
423 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
425 const void *key;
426 void *val;
427 char *newpath;
429 apr_hash_this(hi, &key, NULL, &val);
430 newpath = svn_path_join((const char *) key, path_to_append,
431 apr_hash_pool_get(*output));
432 apr_hash_set(*output, newpath, APR_HASH_KEY_STRING, val);
435 return SVN_NO_ERROR;
439 /* Helper for svn_fs_mergeinfo__get_mergeinfo().
441 Update CACHE (and RESULT iff RESULT is non-null) with mergeinfo for
442 PATH at REV, retrieved from DB.
444 If INHERIT is svn_mergeinfo_explicit, then retrieve only explicit
445 mergeinfo on PATH. Else if it is svn_mergeinfo_nearest_ancestor,
446 then retrieve the mergeinfo for PATH's parent, recursively. Else
447 if it is svn_mergeinfo_inherited, then:
449 - If PATH had any explicit merges committed on or before REV,
450 retrieve the explicit mergeinfo for PATH;
452 - Else, retrieve mergeinfo for PATH's parent, recursively.
454 Perform all allocations in POOL. Due to the nature of APR pools,
455 and the recursion in this function, invoke this function using a
456 sub-pool. To preserve RESULT, use mergeinfo_hash_dup() before
457 clearing or destroying POOL.
459 static svn_error_t *
460 get_mergeinfo_for_path(sqlite3 *db,
461 const char *path,
462 svn_revnum_t rev,
463 apr_hash_t *result,
464 apr_hash_t *cache,
465 svn_mergeinfo_inheritance_t inherit,
466 apr_pool_t *pool)
468 apr_hash_t *path_mergeinfo;
469 svn_fs__sqlite_stmt_t *stmt;
470 svn_revnum_t lastmerged_rev;
472 if (inherit == svn_mergeinfo_nearest_ancestor)
474 /* Looking for (possibly inherited) mergeinfo from PATH ancestors. */
475 lastmerged_rev = 0;
477 else
479 /* Look up the explicit mergeinfo for PATH, starting with the
480 cache, then moving on to the SQLite index. */
481 path_mergeinfo = apr_hash_get(cache, path, APR_HASH_KEY_STRING);
482 if (path_mergeinfo)
484 /* We already had a mergeinfo lookup attempt cached. */
485 if (result && path_mergeinfo != NEGATIVE_CACHE_RESULT)
486 apr_hash_set(result, path, APR_HASH_KEY_STRING, path_mergeinfo);
487 return SVN_NO_ERROR;
490 /* See if we have a mergeinfo_changed record for this path. If not,
491 then it can't have mergeinfo. */
492 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db,
493 "SELECT MAX(revision) FROM "
494 "mergeinfo_changed WHERE path = ? AND "
495 "revision <= ?;", pool));
497 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 1, path));
498 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 2, rev));
499 SVN_ERR(svn_fs__sqlite_step_row(stmt));
501 lastmerged_rev = svn_fs__sqlite_column_revnum(stmt, 0);
502 SVN_ERR(svn_fs__sqlite_finalize(stmt));
504 /* If we've got mergeinfo data, transform it from the db into a
505 mergeinfo hash. Either way, cache whether we found mergeinfo. */
506 if (lastmerged_rev > 0)
508 SVN_ERR(parse_mergeinfo_from_db(db, path, lastmerged_rev,
509 &path_mergeinfo, pool));
510 if (path_mergeinfo)
512 if (result)
513 apr_hash_set(result, path, APR_HASH_KEY_STRING,
514 path_mergeinfo);
515 apr_hash_set(cache, path, APR_HASH_KEY_STRING, path_mergeinfo);
517 else
518 apr_hash_set(cache, path, APR_HASH_KEY_STRING,
519 NEGATIVE_CACHE_RESULT);
520 return SVN_NO_ERROR;
522 } /* inherit != svn_mergeinfo_nearest_ancestor */
524 /* If we want only this path's parent's mergeinfo or this path has no
525 mergeinfo, and we are asked to, check PATH's nearest ancestor. */
526 if ((lastmerged_rev == 0 && inherit == svn_mergeinfo_inherited)
527 || inherit == svn_mergeinfo_nearest_ancestor)
529 svn_stringbuf_t *parentpath;
531 /* It is possible we are already at the root. */
532 if (strcmp(path, "") == 0)
533 return SVN_NO_ERROR;
535 parentpath = svn_stringbuf_create(path, pool);
536 svn_path_remove_component(parentpath);
538 /* The repository and the mergeinfo index internally refer to
539 the root path as "" rather than "/". */
540 if (strcmp(parentpath->data, "/") == 0)
541 svn_stringbuf_set(parentpath, "");
543 SVN_ERR(get_mergeinfo_for_path(db, parentpath->data, rev,
544 NULL, cache, svn_mergeinfo_inherited,
545 pool));
546 path_mergeinfo = apr_hash_get(cache, parentpath->data,
547 APR_HASH_KEY_STRING);
548 if (path_mergeinfo == NEGATIVE_CACHE_RESULT)
549 apr_hash_set(cache, path, APR_HASH_KEY_STRING, NULL);
550 else if (path_mergeinfo)
552 /* Now translate the result for our parent to our path. */
553 apr_hash_t *translated_mergeinfo;
554 const char *to_append = &path[parentpath->len + 1];
556 /* But first remove all non-inheritable revision ranges. */
557 SVN_ERR(svn_mergeinfo_inheritable(&path_mergeinfo, path_mergeinfo,
558 NULL, SVN_INVALID_REVNUM,
559 SVN_INVALID_REVNUM, pool));
560 append_component_to_paths(&translated_mergeinfo, path_mergeinfo,
561 to_append, pool);
562 apr_hash_set(cache, path, APR_HASH_KEY_STRING, translated_mergeinfo);
563 if (result)
564 apr_hash_set(result, path, APR_HASH_KEY_STRING,
565 translated_mergeinfo);
568 return SVN_NO_ERROR;
572 /* Get the mergeinfo for all of the children of PATH in REV. Return
573 the results in PATH_MERGEINFO. PATH_MERGEINFO should already be
574 created prior to calling this function, but it's value may change
575 as additional mergeinfos are added to it. Returned values are
576 allocated in POOL, while temporary values are allocated in a
577 sub-pool. */
578 static svn_error_t *
579 get_mergeinfo_for_children(sqlite3 *db,
580 const char *path,
581 svn_revnum_t rev,
582 apr_hash_t *path_mergeinfo,
583 svn_fs_mergeinfo_filter_func_t filter_func,
584 void *filter_func_baton,
585 apr_pool_t *pool)
587 svn_fs__sqlite_stmt_t *stmt;
588 apr_pool_t *subpool = svn_pool_create(pool);
589 char *like_path;
590 svn_boolean_t got_row;
592 /* Get all paths under us. */
593 SVN_ERR(svn_fs__sqlite_prepare(&stmt, db,
594 "SELECT MAX(revision), path "
595 "FROM mergeinfo_changed "
596 "WHERE path LIKE ? AND revision <= ? "
597 "GROUP BY path;", pool));
599 like_path = apr_psprintf(subpool, "%s/%%", path);
601 SVN_ERR(svn_fs__sqlite_bind_text(stmt, 1, like_path));
602 SVN_ERR(svn_fs__sqlite_bind_int64(stmt, 2, rev));
604 SVN_ERR(svn_fs__sqlite_step(&got_row, stmt));
605 while (got_row)
607 svn_revnum_t lastmerged_rev;
608 const char *merged_path;
610 svn_pool_clear(subpool);
612 lastmerged_rev = svn_fs__sqlite_column_revnum(stmt, 0);
613 merged_path = svn_fs__sqlite_column_text(stmt, 1);
615 /* If we've got a merged revision, go get the mergeinfo from the db */
616 if (lastmerged_rev > 0)
618 apr_hash_t *db_mergeinfo;
619 svn_boolean_t omit = FALSE;
621 SVN_ERR(parse_mergeinfo_from_db(db, merged_path, lastmerged_rev,
622 &db_mergeinfo, subpool));
624 if (filter_func)
625 SVN_ERR(filter_func(filter_func_baton, &omit, merged_path,
626 db_mergeinfo, subpool));
628 if (!omit)
629 SVN_ERR(svn_mergeinfo_merge(path_mergeinfo, db_mergeinfo, pool));
632 SVN_ERR(svn_fs__sqlite_step(&got_row, stmt));
635 SVN_ERR(svn_fs__sqlite_finalize(stmt));
636 svn_pool_destroy(subpool);
638 return SVN_NO_ERROR;
641 /* Return a deep copy of MERGEINFO_HASH (allocated in POOL), which is
642 a hash of paths -> mergeinfo hashes. */
643 static apr_hash_t *
644 mergeinfo_hash_dup(apr_hash_t *mergeinfo_hash, apr_pool_t *pool)
646 apr_hash_t *new_hash = apr_hash_make(pool);
647 apr_hash_index_t *hi;
648 for (hi = apr_hash_first(NULL, mergeinfo_hash); hi; hi = apr_hash_next(hi))
650 const void *path;
651 apr_ssize_t klen;
652 void *mergeinfo;
654 apr_hash_this(hi, &path, &klen, &mergeinfo);
655 apr_hash_set(new_hash, path, klen,
656 svn_mergeinfo_dup((apr_hash_t *) mergeinfo,
657 apr_hash_pool_get(new_hash)));
659 return new_hash;
662 /* Get the mergeinfo for a set of paths, returned in *MERGEINFO_HASH
663 as a hash of mergeinfo hashes keyed by each path. Returned values
664 are allocated in POOL, while temporary values are allocated in a
665 sub-pool. */
666 static svn_error_t *
667 get_mergeinfo(sqlite3 *db,
668 apr_hash_t **mergeinfo_hash,
669 svn_revnum_t rev,
670 const apr_array_header_t *paths,
671 svn_mergeinfo_inheritance_t inherit,
672 apr_pool_t *pool)
674 apr_pool_t *subpool = svn_pool_create(pool);
675 apr_hash_t *result_hash = apr_hash_make(subpool);
676 apr_hash_t *cache_hash = apr_hash_make(subpool);
677 int i;
679 for (i = 0; i < paths->nelts; i++)
681 const char *path = APR_ARRAY_IDX(paths, i, const char *);
682 SVN_ERR(get_mergeinfo_for_path(db, path, rev, result_hash, cache_hash,
683 inherit, apr_hash_pool_get(result_hash)));
686 *mergeinfo_hash = mergeinfo_hash_dup(result_hash, pool);
687 svn_pool_destroy(subpool);
688 return SVN_NO_ERROR;
691 /* Get the mergeinfo for a set of paths. Returned values are
692 allocated in POOL, while temporary values are allocated in a
693 sub-pool. */
694 svn_error_t *
695 svn_fs_mergeinfo__get_mergeinfo(apr_hash_t **mergeinfo,
696 svn_fs_root_t *root,
697 const apr_array_header_t *paths,
698 svn_mergeinfo_inheritance_t inherit,
699 apr_pool_t *pool)
701 sqlite3 *db;
702 int i;
703 svn_error_t *err;
704 svn_revnum_t rev;
705 apr_pool_t *subpool;
707 /* We require a revision root. */
708 if (root->is_txn_root)
709 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
710 rev = REV_ROOT_REV(root);
712 subpool = svn_pool_create(pool);
714 /* Retrieve a path -> mergeinfo hash mapping. */
715 SVN_ERR(svn_fs__sqlite_open(&db, root->fs->path, subpool));
716 err = get_mergeinfo(db, mergeinfo, rev, paths, inherit, pool);
717 SVN_ERR(svn_fs__sqlite_close(db, err));
719 /* Convert each mergeinfo hash value into a textual representation. */
720 for (i = 0; i < paths->nelts; i++)
722 svn_stringbuf_t *mergeinfo_buf;
723 apr_hash_t *path_mergeinfo;
724 const char *path = APR_ARRAY_IDX(paths, i, const char *);
726 svn_pool_clear(subpool);
728 path_mergeinfo = apr_hash_get(*mergeinfo, path, APR_HASH_KEY_STRING);
729 if (path_mergeinfo)
731 SVN_ERR(svn_mergeinfo_sort(path_mergeinfo, subpool));
732 SVN_ERR(svn_mergeinfo_to_stringbuf(&mergeinfo_buf, path_mergeinfo,
733 apr_hash_pool_get(*mergeinfo)));
734 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING,
735 mergeinfo_buf->data);
738 svn_pool_destroy(subpool);
740 return SVN_NO_ERROR;
743 svn_error_t *
744 svn_fs_mergeinfo__get_mergeinfo_for_tree(apr_hash_t **mergeinfo,
745 svn_fs_root_t *root,
746 const apr_array_header_t *paths,
747 svn_fs_mergeinfo_filter_func_t filter_func,
748 void *filter_func_baton,
749 apr_pool_t *pool)
751 svn_error_t *err;
752 svn_revnum_t rev;
753 sqlite3 *db;
754 int i;
756 /* We require a revision root. */
757 if (root->is_txn_root)
758 return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
759 rev = REV_ROOT_REV(root);
761 SVN_ERR(svn_fs__sqlite_open(&db, root->fs->path, pool));
762 err = get_mergeinfo(db, mergeinfo, rev, paths, svn_mergeinfo_inherited,
763 pool);
764 MAYBE_CLEANUP;
766 for (i = 0; i < paths->nelts; i++)
768 const char *path = APR_ARRAY_IDX(paths, i, const char *);
769 apr_hash_t *path_mergeinfo = apr_hash_get(*mergeinfo, path,
770 APR_HASH_KEY_STRING);
772 if (!path_mergeinfo)
773 path_mergeinfo = apr_hash_make(pool);
775 if (filter_func)
777 svn_boolean_t omit;
779 err = filter_func(filter_func_baton, &omit, path, path_mergeinfo,
780 pool);
781 MAYBE_CLEANUP;
783 if (omit)
785 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, NULL);
786 continue;
790 err = get_mergeinfo_for_children(db, path, rev, path_mergeinfo,
791 filter_func, filter_func_baton, pool);
792 MAYBE_CLEANUP;
794 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, path_mergeinfo);
797 cleanup:
798 return svn_fs__sqlite_close(db, err);