]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - subversion/libsvn_fs_fs/temp_serializer.c
Import Subversion-1.10.0
[FreeBSD/FreeBSD.git] / subversion / libsvn_fs_fs / temp_serializer.c
1 /* temp_serializer.c: serialization functions for caching of FSFS structures
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <apr_pools.h>
24
25 #include "svn_pools.h"
26 #include "svn_hash.h"
27 #include "svn_sorts.h"
28 #include "svn_fs.h"
29
30 #include "private/svn_fs_util.h"
31 #include "private/svn_sorts_private.h"
32 #include "private/svn_temp_serializer.h"
33 #include "private/svn_subr_private.h"
34
35 #include "id.h"
36 #include "temp_serializer.h"
37 #include "low_level.h"
38 #include "cached_data.h"
39
40 /* Utility to encode a signed NUMBER into a variable-length sequence of
41  * 8-bit chars in KEY_BUFFER and return the last writen position.
42  *
43  * Numbers will be stored in 7 bits / byte and using byte values above
44  * 32 (' ') to make them combinable with other string by simply separating
45  * individual parts with spaces.
46  */
47 static char*
48 encode_number(apr_int64_t number, char *key_buffer)
49 {
50   /* encode the sign in the first byte */
51   if (number < 0)
52   {
53     number = -number;
54     *key_buffer = (char)((number & 63) + ' ' + 65);
55   }
56   else
57     *key_buffer = (char)((number & 63) + ' ' + 1);
58   number /= 64;
59
60   /* write 7 bits / byte until no significant bits are left */
61   while (number)
62   {
63     *++key_buffer = (char)((number & 127) + ' ' + 1);
64     number /= 128;
65   }
66
67   /* return the last written position */
68   return key_buffer;
69 }
70
71 const char*
72 svn_fs_fs__combine_number_and_string(apr_int64_t number,
73                                      const char *string,
74                                      apr_pool_t *pool)
75 {
76   apr_size_t len = strlen(string);
77
78   /* number part requires max. 10x7 bits + 1 space.
79    * Add another 1 for the terminal 0 */
80   char *key_buffer = apr_palloc(pool, len + 12);
81   const char *key = key_buffer;
82
83   /* Prepend the number to the string and separate them by space. No other
84    * number can result in the same prefix, no other string in the same
85    * postfix nor can the boundary between them be ambiguous. */
86   key_buffer = encode_number(number, key_buffer);
87   *++key_buffer = ' ';
88   memcpy(++key_buffer, string, len+1);
89
90   /* return the start of the key */
91   return key;
92 }
93
94 /* Utility function to serialize string S in the given serialization CONTEXT.
95  */
96 static void
97 serialize_svn_string(svn_temp_serializer__context_t *context,
98                      const svn_string_t * const *s)
99 {
100   const svn_string_t *string = *s;
101
102   /* Nothing to do for NULL string references. */
103   if (string == NULL)
104     return;
105
106   svn_temp_serializer__push(context, (const void * const *)s, sizeof(**s));
107
108   /* the "string" content may actually be arbitrary binary data.
109    * Thus, we cannot use svn_temp_serializer__add_string. */
110   svn_temp_serializer__add_leaf(context,
111                                 (const void * const *)&string->data,
112                                 string->len + 1);
113
114   /* back to the caller's nesting level */
115   svn_temp_serializer__pop(context);
116 }
117
118 /* Utility function to deserialize the STRING inside the BUFFER.
119  */
120 static void
121 deserialize_svn_string(void *buffer, svn_string_t **string)
122 {
123   svn_temp_deserializer__resolve(buffer, (void **)string);
124   if (*string == NULL)
125     return;
126
127   svn_temp_deserializer__resolve(*string, (void **)&(*string)->data);
128 }
129
130 /* Utility function to serialize the REPRESENTATION within the given
131  * serialization CONTEXT.
132  */
133 static void
134 serialize_representation(svn_temp_serializer__context_t *context,
135                          representation_t * const *representation)
136 {
137   const representation_t * rep = *representation;
138   if (rep == NULL)
139     return;
140
141   /* serialize the representation struct itself */
142   svn_temp_serializer__add_leaf(context,
143                                 (const void * const *)representation,
144                                 sizeof(**representation));
145 }
146
147 /* auxiliary structure representing the content of a directory array */
148 typedef struct dir_data_t
149 {
150   /* number of entries in the directory
151    * (it's int because the directory is an APR array) */
152   int count;
153
154   /** Current length of the in-txn in-disk representation of the directory.
155    * SVN_INVALID_FILESIZE if unknown (i.e. committed data). */
156   svn_filesize_t txn_filesize;
157
158   /* number of unused dir entry buckets in the index */
159   apr_size_t over_provision;
160
161   /* internal modifying operations counter
162    * (used to repack data once in a while) */
163   apr_size_t operations;
164
165   /* size of the serialization buffer actually used.
166    * (we will allocate more than we actually need such that we may
167    * append more data in situ later) */
168   apr_size_t len;
169
170   /* reference to the entries */
171   svn_fs_dirent_t **entries;
172
173   /* size of the serialized entries and don't be too wasteful
174    * (needed since the entries are no longer in sequence) */
175   apr_uint32_t *lengths;
176 } dir_data_t;
177
178 /* Utility function to serialize the *ENTRY_P into a the given
179  * serialization CONTEXT. Return the serialized size of the
180  * dir entry in *LENGTH.
181  */
182 static void
183 serialize_dir_entry(svn_temp_serializer__context_t *context,
184                     svn_fs_dirent_t **entry_p,
185                     apr_uint32_t *length)
186 {
187   svn_fs_dirent_t *entry = *entry_p;
188   apr_size_t initial_length = svn_temp_serializer__get_length(context);
189
190   svn_temp_serializer__push(context,
191                             (const void * const *)entry_p,
192                             sizeof(**entry_p));
193
194   svn_fs_fs__id_serialize(context, &entry->id);
195   svn_temp_serializer__add_string(context, &entry->name);
196
197   *length = (apr_uint32_t)(  svn_temp_serializer__get_length(context)
198                            - APR_ALIGN_DEFAULT(initial_length));
199
200   svn_temp_serializer__pop(context);
201 }
202
203 /* Utility function to serialize the DIR into a new serialization
204  * context to be returned. Allocation will be made form POOL.
205  */
206 static svn_temp_serializer__context_t *
207 serialize_dir(svn_fs_fs__dir_data_t *dir, apr_pool_t *pool)
208 {
209   dir_data_t dir_data;
210   int i = 0;
211   svn_temp_serializer__context_t *context;
212   apr_array_header_t *entries = dir->entries;
213
214   /* calculate sizes */
215   int count = entries->nelts;
216   apr_size_t over_provision = 2 + count / 4;
217   apr_size_t total_count = count + over_provision;
218   apr_size_t entries_len = total_count * sizeof(*dir_data.entries);
219   apr_size_t lengths_len = total_count * sizeof(*dir_data.lengths);
220
221   /* copy the hash entries to an auxiliary struct of known layout */
222   dir_data.count = count;
223   dir_data.txn_filesize = dir->txn_filesize;
224   dir_data.over_provision = over_provision;
225   dir_data.operations = 0;
226   dir_data.entries = apr_palloc(pool, entries_len);
227   dir_data.lengths = apr_palloc(pool, lengths_len);
228
229   for (i = 0; i < count; ++i)
230     dir_data.entries[i] = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
231
232   /* Serialize that aux. structure into a new one. Also, provide a good
233    * estimate for the size of the buffer that we will need. */
234   context = svn_temp_serializer__init(&dir_data,
235                                       sizeof(dir_data),
236                                       50 + count * 200 + entries_len,
237                                       pool);
238
239   /* serialize entries references */
240   svn_temp_serializer__push(context,
241                             (const void * const *)&dir_data.entries,
242                             entries_len);
243
244   /* serialize the individual entries and their sub-structures */
245   for (i = 0; i < count; ++i)
246     serialize_dir_entry(context,
247                         &dir_data.entries[i],
248                         &dir_data.lengths[i]);
249
250   svn_temp_serializer__pop(context);
251
252   /* serialize entries references */
253   svn_temp_serializer__push(context,
254                             (const void * const *)&dir_data.lengths,
255                             lengths_len);
256
257   return context;
258 }
259
260 /* Utility function to reconstruct a dir entries struct from serialized data
261  * in BUFFER and DIR_DATA. Allocation will be made form POOL.
262  */
263 static svn_fs_fs__dir_data_t *
264 deserialize_dir(void *buffer, dir_data_t *dir_data, apr_pool_t *pool)
265 {
266   svn_fs_fs__dir_data_t *result;
267   apr_size_t i;
268   apr_size_t count;
269   svn_fs_dirent_t *entry;
270   svn_fs_dirent_t **entries;
271
272   /* Construct empty directory object. */
273   result = apr_pcalloc(pool, sizeof(*result));
274   result->entries
275     = apr_array_make(pool, dir_data->count, sizeof(svn_fs_dirent_t *));
276   result->txn_filesize = dir_data->txn_filesize;
277
278   /* resolve the reference to the entries array */
279   svn_temp_deserializer__resolve(buffer, (void **)&dir_data->entries);
280   entries = dir_data->entries;
281
282   /* fixup the references within each entry and add it to the RESULT */
283   for (i = 0, count = dir_data->count; i < count; ++i)
284     {
285       svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
286       entry = dir_data->entries[i];
287
288       /* pointer fixup */
289       svn_temp_deserializer__resolve(entry, (void **)&entry->name);
290       svn_fs_fs__id_deserialize(entry, (svn_fs_id_t **)&entry->id);
291
292       /* add the entry to the hash */
293       APR_ARRAY_PUSH(result->entries, svn_fs_dirent_t *) = entry;
294     }
295
296   /* return the now complete hash */
297   return result;
298 }
299
300 void
301 svn_fs_fs__noderev_serialize(svn_temp_serializer__context_t *context,
302                              node_revision_t * const *noderev_p)
303 {
304   const node_revision_t *noderev = *noderev_p;
305   if (noderev == NULL)
306     return;
307
308   /* serialize the representation struct itself */
309   svn_temp_serializer__push(context,
310                             (const void * const *)noderev_p,
311                             sizeof(*noderev));
312
313   /* serialize sub-structures */
314   svn_fs_fs__id_serialize(context, &noderev->id);
315   svn_fs_fs__id_serialize(context, &noderev->predecessor_id);
316   serialize_representation(context, &noderev->prop_rep);
317   serialize_representation(context, &noderev->data_rep);
318
319   svn_temp_serializer__add_string(context, &noderev->copyfrom_path);
320   svn_temp_serializer__add_string(context, &noderev->copyroot_path);
321   svn_temp_serializer__add_string(context, &noderev->created_path);
322
323   /* return to the caller's nesting level */
324   svn_temp_serializer__pop(context);
325 }
326
327
328 void
329 svn_fs_fs__noderev_deserialize(void *buffer,
330                                node_revision_t **noderev_p)
331 {
332   node_revision_t *noderev;
333
334   /* fixup the reference to the representation itself,
335    * if this is part of a parent structure. */
336   if (buffer != *noderev_p)
337     svn_temp_deserializer__resolve(buffer, (void **)noderev_p);
338
339   noderev = *noderev_p;
340   if (noderev == NULL)
341     return;
342
343   /* fixup of sub-structures */
344   svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->id);
345   svn_fs_fs__id_deserialize(noderev, (svn_fs_id_t **)&noderev->predecessor_id);
346   svn_temp_deserializer__resolve(noderev, (void **)&noderev->prop_rep);
347   svn_temp_deserializer__resolve(noderev, (void **)&noderev->data_rep);
348
349   svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyfrom_path);
350   svn_temp_deserializer__resolve(noderev, (void **)&noderev->copyroot_path);
351   svn_temp_deserializer__resolve(noderev, (void **)&noderev->created_path);
352 }
353
354 svn_error_t *
355 svn_fs_fs__serialize_raw_window(void **buffer,
356                                 apr_size_t *buffer_size,
357                                 void *item,
358                                 apr_pool_t *pool)
359 {
360   svn_fs_fs__raw_cached_window_t *window = item;
361   svn_stringbuf_t *serialized;
362
363   /* initialize the serialization process and allocate a buffer large
364    * enough to do prevent re-allocations. */
365   svn_temp_serializer__context_t *context =
366       svn_temp_serializer__init(window,
367                                 sizeof(*window),
368                                 sizeof(*window) + window->window.len + 16,
369                                 pool);
370
371   /* serialize the sub-structure(s) */
372   svn_temp_serializer__add_leaf(context,
373                                 (const void * const *)&window->window.data,
374                                 window->window.len + 1);
375
376   /* return the serialized result */
377   serialized = svn_temp_serializer__get(context);
378
379   *buffer = serialized->data;
380   *buffer_size = serialized->len;
381
382   return SVN_NO_ERROR;
383 }
384
385 svn_error_t *
386 svn_fs_fs__deserialize_raw_window(void **item,
387                                   void *buffer,
388                                   apr_size_t buffer_size,
389                                   apr_pool_t *pool)
390 {
391   svn_fs_fs__raw_cached_window_t *window =
392       (svn_fs_fs__raw_cached_window_t *)buffer;
393
394   /* pointer reference fixup */
395   svn_temp_deserializer__resolve(window, (void **)&window->window.data);
396
397   /* done */
398   *item = buffer;
399
400   return SVN_NO_ERROR;
401 }
402
403
404 /* Utility function to serialize COUNT svn_txdelta_op_t objects
405  * at OPS in the given serialization CONTEXT.
406  */
407 static void
408 serialize_txdelta_ops(svn_temp_serializer__context_t *context,
409                       const svn_txdelta_op_t * const * ops,
410                       apr_size_t count)
411 {
412   if (*ops == NULL)
413     return;
414
415   /* the ops form a contiguous chunk of memory with no further references */
416   svn_temp_serializer__add_leaf(context,
417                                 (const void * const *)ops,
418                                 count * sizeof(**ops));
419 }
420
421 /* Utility function to serialize W in the given serialization CONTEXT.
422  */
423 static void
424 serialize_txdeltawindow(svn_temp_serializer__context_t *context,
425                         svn_txdelta_window_t * const * w)
426 {
427   svn_txdelta_window_t *window = *w;
428
429   /* serialize the window struct itself */
430   svn_temp_serializer__push(context, (const void * const *)w, sizeof(**w));
431
432   /* serialize its sub-structures */
433   serialize_txdelta_ops(context, &window->ops, window->num_ops);
434   serialize_svn_string(context, &window->new_data);
435
436   svn_temp_serializer__pop(context);
437 }
438
439 svn_error_t *
440 svn_fs_fs__serialize_txdelta_window(void **buffer,
441                                     apr_size_t *buffer_size,
442                                     void *item,
443                                     apr_pool_t *pool)
444 {
445   svn_fs_fs__txdelta_cached_window_t *window_info = item;
446   svn_stringbuf_t *serialized;
447
448   /* initialize the serialization process and allocate a buffer large
449    * enough to do without the need of re-allocations in most cases. */
450   apr_size_t text_len = window_info->window->new_data
451                       ? window_info->window->new_data->len
452                       : 0;
453   svn_temp_serializer__context_t *context =
454       svn_temp_serializer__init(window_info,
455                                 sizeof(*window_info),
456                                 500 + text_len,
457                                 pool);
458
459   /* serialize the sub-structure(s) */
460   serialize_txdeltawindow(context, &window_info->window);
461
462   /* return the serialized result */
463   serialized = svn_temp_serializer__get(context);
464
465   *buffer = serialized->data;
466   *buffer_size = serialized->len;
467
468   return SVN_NO_ERROR;
469 }
470
471 svn_error_t *
472 svn_fs_fs__deserialize_txdelta_window(void **item,
473                                       void *buffer,
474                                       apr_size_t buffer_size,
475                                       apr_pool_t *pool)
476 {
477   svn_txdelta_window_t *window;
478
479   /* Copy the _full_ buffer as it also contains the sub-structures. */
480   svn_fs_fs__txdelta_cached_window_t *window_info =
481       (svn_fs_fs__txdelta_cached_window_t *)buffer;
482
483   /* pointer reference fixup */
484   svn_temp_deserializer__resolve(window_info,
485                                  (void **)&window_info->window);
486   window = window_info->window;
487
488   svn_temp_deserializer__resolve(window, (void **)&window->ops);
489
490   deserialize_svn_string(window, (svn_string_t**)&window->new_data);
491
492   /* done */
493   *item = window_info;
494
495   return SVN_NO_ERROR;
496 }
497
498 svn_error_t *
499 svn_fs_fs__serialize_manifest(void **data,
500                               apr_size_t *data_len,
501                               void *in,
502                               apr_pool_t *pool)
503 {
504   apr_array_header_t *manifest = in;
505
506   *data_len = sizeof(apr_off_t) *manifest->nelts;
507   *data = apr_pmemdup(pool, manifest->elts, *data_len);
508
509   return SVN_NO_ERROR;
510 }
511
512 svn_error_t *
513 svn_fs_fs__deserialize_manifest(void **out,
514                                 void *data,
515                                 apr_size_t data_len,
516                                 apr_pool_t *pool)
517 {
518   apr_array_header_t *manifest = apr_array_make(pool, 1, sizeof(apr_off_t));
519
520   manifest->nelts = (int) (data_len / sizeof(apr_off_t));
521   manifest->nalloc = (int) (data_len / sizeof(apr_off_t));
522   manifest->elts = (char*)data;
523
524   *out = manifest;
525
526   return SVN_NO_ERROR;
527 }
528
529 /* Auxiliary structure representing the content of a properties hash.
530    This structure is much easier to (de-)serialize than an apr_hash.
531  */
532 typedef struct properties_data_t
533 {
534   /* number of entries in the hash */
535   apr_size_t count;
536
537   /* reference to the keys */
538   const char **keys;
539
540   /* reference to the values */
541   const svn_string_t **values;
542 } properties_data_t;
543
544 /* Serialize COUNT C-style strings from *STRINGS into CONTEXT. */
545 static void
546 serialize_cstring_array(svn_temp_serializer__context_t *context,
547                         const char ***strings,
548                         apr_size_t count)
549 {
550   apr_size_t i;
551   const char **entries = *strings;
552
553   /* serialize COUNT entries pointers (the array) */
554   svn_temp_serializer__push(context,
555                             (const void * const *)strings,
556                             count * sizeof(const char*));
557
558   /* serialize array elements */
559   for (i = 0; i < count; ++i)
560     svn_temp_serializer__add_string(context, &entries[i]);
561
562   svn_temp_serializer__pop(context);
563 }
564
565 /* Serialize COUNT svn_string_t* items from *STRINGS into CONTEXT. */
566 static void
567 serialize_svn_string_array(svn_temp_serializer__context_t *context,
568                            const svn_string_t ***strings,
569                            apr_size_t count)
570 {
571   apr_size_t i;
572   const svn_string_t **entries = *strings;
573
574   /* serialize COUNT entries pointers (the array) */
575   svn_temp_serializer__push(context,
576                             (const void * const *)strings,
577                             count * sizeof(const char*));
578
579   /* serialize array elements */
580   for (i = 0; i < count; ++i)
581     serialize_svn_string(context, &entries[i]);
582
583   svn_temp_serializer__pop(context);
584 }
585
586 svn_error_t *
587 svn_fs_fs__serialize_properties(void **data,
588                                 apr_size_t *data_len,
589                                 void *in,
590                                 apr_pool_t *pool)
591 {
592   apr_hash_t *hash = in;
593   properties_data_t properties;
594   svn_temp_serializer__context_t *context;
595   apr_hash_index_t *hi;
596   svn_stringbuf_t *serialized;
597   apr_size_t i;
598
599   /* create our auxiliary data structure */
600   properties.count = apr_hash_count(hash);
601   properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
602   properties.values = apr_palloc(pool, sizeof(const svn_string_t *) * properties.count);
603
604   /* populate it with the hash entries */
605   for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
606     {
607       properties.keys[i] = apr_hash_this_key(hi);
608       properties.values[i] = apr_hash_this_val(hi);
609     }
610
611   /* serialize it */
612   context = svn_temp_serializer__init(&properties,
613                                       sizeof(properties),
614                                       properties.count * 100,
615                                       pool);
616
617   properties.keys[i] = "";
618   serialize_cstring_array(context, &properties.keys, properties.count + 1);
619   serialize_svn_string_array(context, &properties.values, properties.count);
620
621   /* return the serialized result */
622   serialized = svn_temp_serializer__get(context);
623
624   *data = serialized->data;
625   *data_len = serialized->len;
626
627   return SVN_NO_ERROR;
628 }
629
630 svn_error_t *
631 svn_fs_fs__deserialize_properties(void **out,
632                                   void *data,
633                                   apr_size_t data_len,
634                                   apr_pool_t *pool)
635 {
636   apr_hash_t *hash = svn_hash__make(pool);
637   properties_data_t *properties = (properties_data_t *)data;
638   size_t i;
639
640   /* de-serialize our auxiliary data structure */
641   svn_temp_deserializer__resolve(properties, (void**)&properties->keys);
642   svn_temp_deserializer__resolve(properties, (void**)&properties->values);
643
644   /* de-serialize each entry and put it into the hash */
645   for (i = 0; i < properties->count; ++i)
646     {
647       apr_size_t len = properties->keys[i+1] - properties->keys[i] - 1;
648       svn_temp_deserializer__resolve(properties->keys,
649                                      (void**)&properties->keys[i]);
650
651       deserialize_svn_string(properties->values,
652                              (svn_string_t **)&properties->values[i]);
653
654       apr_hash_set(hash,
655                    properties->keys[i], len,
656                    properties->values[i]);
657     }
658
659   /* done */
660   *out = hash;
661
662   return SVN_NO_ERROR;
663 }
664
665 svn_error_t *
666 svn_fs_fs__serialize_revprops(void **data,
667                               apr_size_t *data_len,
668                               void *in,
669                               apr_pool_t *pool)
670 {
671   svn_string_t *buffer = in;
672
673   *data = (void *)buffer->data;
674   *data_len = buffer->len;
675
676   return SVN_NO_ERROR;
677 }
678
679 svn_error_t *
680 svn_fs_fs__deserialize_revprops(void **out,
681                                 void *data,
682                                 apr_size_t data_len,
683                                 apr_pool_t *pool)
684 {
685   apr_hash_t *properties;
686   svn_stream_t *stream;
687
688   svn_string_t buffer;
689   buffer.data = data;
690   buffer.len = data_len;
691
692   stream = svn_stream_from_string(&buffer, pool);
693   properties = svn_hash__make(pool);
694
695   SVN_ERR(svn_hash_read2(properties, stream, SVN_HASH_TERMINATOR, pool));
696
697   /* done */
698   *out = properties;
699
700   return SVN_NO_ERROR;
701 }
702
703 svn_error_t *
704 svn_fs_fs__serialize_id(void **data,
705                         apr_size_t *data_len,
706                         void *in,
707                         apr_pool_t *pool)
708 {
709   const svn_fs_id_t *id = in;
710   svn_stringbuf_t *serialized;
711
712   /* create an (empty) serialization context with plenty of buffer space */
713   svn_temp_serializer__context_t *context =
714       svn_temp_serializer__init(NULL, 0, 250, pool);
715
716   /* serialize the id */
717   svn_fs_fs__id_serialize(context, &id);
718
719   /* return serialized data */
720   serialized = svn_temp_serializer__get(context);
721   *data = serialized->data;
722   *data_len = serialized->len;
723
724   return SVN_NO_ERROR;
725 }
726
727 svn_error_t *
728 svn_fs_fs__deserialize_id(void **out,
729                           void *data,
730                           apr_size_t data_len,
731                           apr_pool_t *pool)
732 {
733   /* Copy the _full_ buffer as it also contains the sub-structures. */
734   svn_fs_id_t *id = (svn_fs_id_t *)data;
735
736   /* fixup of all pointers etc. */
737   svn_fs_fs__id_deserialize(id, &id);
738
739   /* done */
740   *out = id;
741   return SVN_NO_ERROR;
742 }
743
744 /** Caching node_revision_t objects. **/
745
746 svn_error_t *
747 svn_fs_fs__serialize_node_revision(void **buffer,
748                                    apr_size_t *buffer_size,
749                                    void *item,
750                                    apr_pool_t *pool)
751 {
752   svn_stringbuf_t *serialized;
753   node_revision_t *noderev = item;
754
755   /* create an (empty) serialization context with plenty of (initial)
756    * buffer space. */
757   svn_temp_serializer__context_t *context =
758       svn_temp_serializer__init(NULL, 0,
759                                 1024 - SVN_TEMP_SERIALIZER__OVERHEAD,
760                                 pool);
761
762   /* serialize the noderev */
763   svn_fs_fs__noderev_serialize(context, &noderev);
764
765   /* return serialized data */
766   serialized = svn_temp_serializer__get(context);
767   *buffer = serialized->data;
768   *buffer_size = serialized->len;
769
770   return SVN_NO_ERROR;
771 }
772
773 svn_error_t *
774 svn_fs_fs__deserialize_node_revision(void **item,
775                                      void *buffer,
776                                      apr_size_t buffer_size,
777                                      apr_pool_t *pool)
778 {
779   /* Copy the _full_ buffer as it also contains the sub-structures. */
780   node_revision_t *noderev = (node_revision_t *)buffer;
781
782   /* fixup of all pointers etc. */
783   svn_fs_fs__noderev_deserialize(noderev, &noderev);
784
785   /* done */
786   *item = noderev;
787   return SVN_NO_ERROR;
788 }
789
790 /* Utility function that returns the directory serialized inside CONTEXT
791  * to DATA and DATA_LEN.  If OVERPROVISION is set, allocate some extra
792  * room for future in-place changes by svn_fs_fs__replace_dir_entry. */
793 static svn_error_t *
794 return_serialized_dir_context(svn_temp_serializer__context_t *context,
795                               void **data,
796                               apr_size_t *data_len,
797                               svn_boolean_t overprovision)
798 {
799   svn_stringbuf_t *serialized = svn_temp_serializer__get(context);
800
801   *data = serialized->data;
802   *data_len = overprovision ? serialized->blocksize : serialized->len;
803   ((dir_data_t *)serialized->data)->len = serialized->len;
804
805   return SVN_NO_ERROR;
806 }
807
808 svn_error_t *
809 svn_fs_fs__serialize_dir_entries(void **data,
810                                  apr_size_t *data_len,
811                                  void *in,
812                                  apr_pool_t *pool)
813 {
814   svn_fs_fs__dir_data_t *dir = in;
815
816   /* serialize the dir content into a new serialization context
817    * and return the serialized data */
818   return return_serialized_dir_context(serialize_dir(dir, pool),
819                                        data,
820                                        data_len,
821                                        FALSE);
822 }
823
824 svn_error_t *
825 svn_fs_fs__serialize_txndir_entries(void **data,
826                                     apr_size_t *data_len,
827                                     void *in,
828                                     apr_pool_t *pool)
829 {
830   svn_fs_fs__dir_data_t *dir = in;
831
832   /* serialize the dir content into a new serialization context
833    * and return the serialized data */
834   return return_serialized_dir_context(serialize_dir(dir, pool),
835                                        data,
836                                        data_len,
837                                        TRUE);
838 }
839
840 svn_error_t *
841 svn_fs_fs__deserialize_dir_entries(void **out,
842                                    void *data,
843                                    apr_size_t data_len,
844                                    apr_pool_t *pool)
845 {
846   /* Copy the _full_ buffer as it also contains the sub-structures. */
847   dir_data_t *dir_data = (dir_data_t *)data;
848
849   /* reconstruct the hash from the serialized data */
850   *out = deserialize_dir(dir_data, dir_data, pool);
851
852   return SVN_NO_ERROR;
853 }
854
855 svn_error_t *
856 svn_fs_fs__get_sharded_offset(void **out,
857                               const void *data,
858                               apr_size_t data_len,
859                               void *baton,
860                               apr_pool_t *pool)
861 {
862   const apr_off_t *manifest = data;
863   apr_int64_t shard_pos = *(apr_int64_t *)baton;
864
865   *(apr_off_t *)out = manifest[shard_pos];
866
867   return SVN_NO_ERROR;
868 }
869
870 svn_error_t *
871 svn_fs_fs__extract_dir_filesize(void **out,
872                                 const void *data,
873                                 apr_size_t data_len,
874                                 void *baton,
875                                 apr_pool_t *pool)
876 {
877   const dir_data_t *dir_data = data;
878
879   *(svn_filesize_t *)out = dir_data->txn_filesize;
880
881   return SVN_NO_ERROR;
882 }
883
884 /* Utility function that returns the lowest index of the first entry in
885  * *ENTRIES that points to a dir entry with a name equal or larger than NAME.
886  * If an exact match has been found, *FOUND will be set to TRUE. COUNT is
887  * the number of valid entries in ENTRIES.
888  */
889 static apr_size_t
890 find_entry(svn_fs_dirent_t **entries,
891            const char *name,
892            apr_size_t count,
893            svn_boolean_t *found)
894 {
895   /* binary search for the desired entry by name */
896   apr_size_t lower = 0;
897   apr_size_t upper = count;
898   apr_size_t middle;
899
900   for (middle = upper / 2; lower < upper; middle = (upper + lower) / 2)
901     {
902       const svn_fs_dirent_t *entry =
903           svn_temp_deserializer__ptr(entries, (const void *const *)&entries[middle]);
904       const char* entry_name =
905           svn_temp_deserializer__ptr(entry, (const void *const *)&entry->name);
906
907       int diff = strcmp(entry_name, name);
908       if (diff < 0)
909         lower = middle + 1;
910       else
911         upper = middle;
912     }
913
914   /* check whether we actually found a match */
915   *found = FALSE;
916   if (lower < count)
917     {
918       const svn_fs_dirent_t *entry =
919           svn_temp_deserializer__ptr(entries, (const void *const *)&entries[lower]);
920       const char* entry_name =
921           svn_temp_deserializer__ptr(entry, (const void *const *)&entry->name);
922
923       if (strcmp(entry_name, name) == 0)
924         *found = TRUE;
925     }
926
927   return lower;
928 }
929
930 svn_error_t *
931 svn_fs_fs__extract_dir_entry(void **out,
932                              const void *data,
933                              apr_size_t data_len,
934                              void *baton,
935                              apr_pool_t *pool)
936 {
937   const dir_data_t *dir_data = data;
938   extract_dir_entry_baton_t *entry_baton = baton;
939   svn_boolean_t found;
940
941   /* resolve the reference to the entries array */
942   const svn_fs_dirent_t * const *entries =
943     svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->entries);
944
945   /* resolve the reference to the lengths array */
946   const apr_uint32_t *lengths =
947     svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->lengths);
948
949   /* binary search for the desired entry by name */
950   apr_size_t pos = find_entry((svn_fs_dirent_t **)entries,
951                               entry_baton->name,
952                               dir_data->count,
953                               &found);
954
955   /* de-serialize that entry or return NULL, if no match has been found.
956    * Be sure to check that the directory contents is still up-to-date. */
957   entry_baton->out_of_date
958     = dir_data->txn_filesize != entry_baton->txn_filesize;
959
960   *out = NULL;
961   if (found && !entry_baton->out_of_date)
962     {
963       const svn_fs_dirent_t *source =
964           svn_temp_deserializer__ptr(entries, (const void *const *)&entries[pos]);
965
966       /* Entries have been serialized one-by-one, each time including all
967        * nested structures and strings. Therefore, they occupy a single
968        * block of memory whose end-offset is either the beginning of the
969        * next entry or the end of the buffer
970        */
971       apr_size_t size = lengths[pos];
972
973       /* copy & deserialize the entry */
974       svn_fs_dirent_t *new_entry = apr_pmemdup(pool, source, size);
975
976       svn_temp_deserializer__resolve(new_entry, (void **)&new_entry->name);
977       svn_fs_fs__id_deserialize(new_entry, (svn_fs_id_t **)&new_entry->id);
978       *(svn_fs_dirent_t **)out = new_entry;
979     }
980
981   return SVN_NO_ERROR;
982 }
983
984 /* Utility function for svn_fs_fs__replace_dir_entry that implements the
985  * modification as a simply deserialize / modify / serialize sequence.
986  */
987 static svn_error_t *
988 slowly_replace_dir_entry(void **data,
989                          apr_size_t *data_len,
990                          void *baton,
991                          apr_pool_t *pool)
992 {
993   replace_baton_t *replace_baton = (replace_baton_t *)baton;
994   dir_data_t *dir_data = (dir_data_t *)*data;
995   svn_fs_fs__dir_data_t *dir;
996   int idx = -1;
997   svn_fs_dirent_t *entry;
998   apr_array_header_t *entries;
999
1000   SVN_ERR(svn_fs_fs__deserialize_dir_entries((void **)&dir,
1001                                              *data,
1002                                              dir_data->len,
1003                                              pool));
1004
1005   entries = dir->entries;
1006   entry = svn_fs_fs__find_dir_entry(entries, replace_baton->name, &idx);
1007
1008   /* Replacement or removal? */
1009   if (replace_baton->new_entry)
1010     {
1011       /* Replace ENTRY with / insert the NEW_ENTRY */
1012       if (entry)
1013         APR_ARRAY_IDX(entries, idx, svn_fs_dirent_t *)
1014           = replace_baton->new_entry;
1015       else
1016         svn_sort__array_insert(entries, &replace_baton->new_entry, idx);
1017     }
1018   else
1019     {
1020       /* Remove the old ENTRY. */
1021       if (entry)
1022         svn_sort__array_delete(entries, idx, 1);
1023     }
1024
1025   return svn_fs_fs__serialize_dir_entries(data, data_len, dir, pool);
1026 }
1027
1028 svn_error_t *
1029 svn_fs_fs__replace_dir_entry(void **data,
1030                              apr_size_t *data_len,
1031                              void *baton,
1032                              apr_pool_t *pool)
1033 {
1034   replace_baton_t *replace_baton = (replace_baton_t *)baton;
1035   dir_data_t *dir_data = (dir_data_t *)*data;
1036   svn_boolean_t found;
1037   svn_fs_dirent_t **entries;
1038   apr_uint32_t *lengths;
1039   apr_uint32_t length;
1040   apr_size_t pos;
1041
1042   svn_temp_serializer__context_t *context;
1043
1044   /* update the cached file length info.
1045    * Because we are writing to the cache, it is fair to assume that the
1046    * caller made sure that the current contents is consistent with the
1047    * previous state of the directory file. */
1048   dir_data->txn_filesize = replace_baton->txn_filesize;
1049
1050   /* after quite a number of operations, let's re-pack everything.
1051    * This is to limit the number of wasted space as we cannot overwrite
1052    * existing data but must always append. */
1053   if (dir_data->operations > 2 + dir_data->count / 4)
1054     return slowly_replace_dir_entry(data, data_len, baton, pool);
1055
1056   /* resolve the reference to the entries array */
1057   entries = (svn_fs_dirent_t **)
1058     svn_temp_deserializer__ptr(dir_data,
1059                                (const void *const *)&dir_data->entries);
1060
1061   /* resolve the reference to the lengths array */
1062   lengths = (apr_uint32_t *)
1063     svn_temp_deserializer__ptr(dir_data,
1064                                (const void *const *)&dir_data->lengths);
1065
1066   /* binary search for the desired entry by name */
1067   pos = find_entry(entries, replace_baton->name, dir_data->count, &found);
1068
1069   /* handle entry removal (if found at all) */
1070   if (replace_baton->new_entry == NULL)
1071     {
1072       if (found)
1073         {
1074           /* remove reference to the entry from the index */
1075           memmove(&entries[pos],
1076                   &entries[pos + 1],
1077                   sizeof(entries[pos]) * (dir_data->count - pos));
1078           memmove(&lengths[pos],
1079                   &lengths[pos + 1],
1080                   sizeof(lengths[pos]) * (dir_data->count - pos));
1081
1082           dir_data->count--;
1083           dir_data->over_provision++;
1084           dir_data->operations++;
1085         }
1086
1087       return SVN_NO_ERROR;
1088     }
1089
1090   /* if not found, prepare to insert the new entry */
1091   if (!found)
1092     {
1093       /* fallback to slow operation if there is no place left to insert an
1094        * new entry to index. That will automatically give add some spare
1095        * entries ("overprovision"). */
1096       if (dir_data->over_provision == 0)
1097         return slowly_replace_dir_entry(data, data_len, baton, pool);
1098
1099       /* make entries[index] available for pointing to the new entry */
1100       memmove(&entries[pos + 1],
1101               &entries[pos],
1102               sizeof(entries[pos]) * (dir_data->count - pos));
1103       memmove(&lengths[pos + 1],
1104               &lengths[pos],
1105               sizeof(lengths[pos]) * (dir_data->count - pos));
1106
1107       dir_data->count++;
1108       dir_data->over_provision--;
1109       dir_data->operations++;
1110     }
1111
1112   /* de-serialize the new entry */
1113   entries[pos] = replace_baton->new_entry;
1114   context = svn_temp_serializer__init_append(dir_data,
1115                                              entries,
1116                                              dir_data->len,
1117                                              *data_len,
1118                                              pool);
1119   serialize_dir_entry(context, &entries[pos], &length);
1120
1121   /* return the updated serialized data */
1122   SVN_ERR(return_serialized_dir_context(context, data, data_len, TRUE));
1123
1124   /* since the previous call may have re-allocated the buffer, the lengths
1125    * pointer may no longer point to the entry in that buffer. Therefore,
1126    * re-map it again and store the length value after that. */
1127
1128   dir_data = (dir_data_t *)*data;
1129   lengths = (apr_uint32_t *)
1130     svn_temp_deserializer__ptr(dir_data,
1131                                (const void *const *)&dir_data->lengths);
1132   lengths[pos] = length;
1133
1134   return SVN_NO_ERROR;
1135 }
1136
1137 svn_error_t *
1138 svn_fs_fs__reset_txn_filesize(void **data,
1139                               apr_size_t *data_len,
1140                               void *baton,
1141                               apr_pool_t *pool)
1142 {
1143   dir_data_t *dir_data = (dir_data_t *)*data;
1144   dir_data->txn_filesize = SVN_INVALID_FILESIZE;
1145
1146   return SVN_NO_ERROR;
1147 }
1148
1149 svn_error_t  *
1150 svn_fs_fs__serialize_rep_header(void **data,
1151                                 apr_size_t *data_len,
1152                                 void *in,
1153                                 apr_pool_t *pool)
1154 {
1155   svn_fs_fs__rep_header_t *copy = apr_palloc(pool, sizeof(*copy));
1156   *copy = *(svn_fs_fs__rep_header_t *)in;
1157
1158   *data_len = sizeof(*copy);
1159   *data = copy;
1160
1161   return SVN_NO_ERROR;
1162 }
1163
1164 svn_error_t *
1165 svn_fs_fs__deserialize_rep_header(void **out,
1166                                   void *data,
1167                                   apr_size_t data_len,
1168                                   apr_pool_t *pool)
1169 {
1170   svn_fs_fs__rep_header_t *copy = apr_palloc(pool, sizeof(*copy));
1171   SVN_ERR_ASSERT(data_len == sizeof(*copy));
1172
1173   *copy = *(svn_fs_fs__rep_header_t *)data;
1174   *out = data;
1175
1176   return SVN_NO_ERROR;
1177 }
1178
1179 /* Utility function to serialize change CHANGE_P in the given serialization
1180  * CONTEXT.
1181  */
1182 static void
1183 serialize_change(svn_temp_serializer__context_t *context,
1184                  change_t * const *change_p)
1185 {
1186   const change_t * change = *change_p;
1187   if (change == NULL)
1188     return;
1189
1190   /* serialize the change struct itself */
1191   svn_temp_serializer__push(context,
1192                             (const void * const *)change_p,
1193                             sizeof(*change));
1194
1195   /* serialize sub-structures */
1196   svn_fs_fs__id_serialize(context, &change->info.node_rev_id);
1197
1198   svn_temp_serializer__add_string(context, &change->path.data);
1199   svn_temp_serializer__add_string(context, &change->info.copyfrom_path);
1200
1201   /* return to the caller's nesting level */
1202   svn_temp_serializer__pop(context);
1203 }
1204
1205 /* Utility function to serialize the CHANGE_P within the given
1206  * serialization CONTEXT.
1207  */
1208 static void
1209 deserialize_change(void *buffer, change_t **change_p)
1210 {
1211   change_t * change;
1212
1213   /* fix-up of the pointer to the struct in question */
1214   svn_temp_deserializer__resolve(buffer, (void **)change_p);
1215
1216   change = *change_p;
1217   if (change == NULL)
1218     return;
1219
1220   /* fix-up of sub-structures */
1221   svn_fs_fs__id_deserialize(change, (svn_fs_id_t **)&change->info.node_rev_id);
1222
1223   svn_temp_deserializer__resolve(change, (void **)&change->path.data);
1224   svn_temp_deserializer__resolve(change, (void **)&change->info.copyfrom_path);
1225 }
1226
1227 svn_error_t *
1228 svn_fs_fs__serialize_changes(void **data,
1229                              apr_size_t *data_len,
1230                              void *in,
1231                              apr_pool_t *pool)
1232 {
1233   svn_fs_fs__changes_list_t *changes = in;
1234   svn_temp_serializer__context_t *context;
1235   svn_stringbuf_t *serialized;
1236   int i;
1237
1238   /* serialize it and all its elements */
1239   context = svn_temp_serializer__init(changes,
1240                                       sizeof(*changes),
1241                                       changes->count * 250,
1242                                       pool);
1243
1244   svn_temp_serializer__push(context,
1245                             (const void * const *)&changes->changes,
1246                             changes->count * sizeof(*changes->changes));
1247
1248   for (i = 0; i < changes->count; ++i)
1249     serialize_change(context, &changes->changes[i]);
1250
1251   svn_temp_serializer__pop(context);
1252
1253   /* return the serialized result */
1254   serialized = svn_temp_serializer__get(context);
1255
1256   *data = serialized->data;
1257   *data_len = serialized->len;
1258
1259   return SVN_NO_ERROR;
1260 }
1261
1262 svn_error_t *
1263 svn_fs_fs__deserialize_changes(void **out,
1264                                void *data,
1265                                apr_size_t data_len,
1266                                apr_pool_t *pool)
1267 {
1268   int i;
1269   svn_fs_fs__changes_list_t *changes = (svn_fs_fs__changes_list_t *)data;
1270
1271   /* de-serialize our auxiliary data structure */
1272   svn_temp_deserializer__resolve(changes, (void**)&changes->changes);
1273
1274   /* de-serialize each entry and add it to the array */
1275   for (i = 0; i < changes->count; ++i)
1276     deserialize_change(changes->changes,
1277                        (change_t **)&changes->changes[i]);
1278
1279   /* done */
1280   *out = changes;
1281
1282   return SVN_NO_ERROR;
1283 }
1284
1285 /* Auxiliary structure representing the content of a svn_mergeinfo_t hash.
1286    This structure is much easier to (de-)serialize than an APR array.
1287  */
1288 typedef struct mergeinfo_data_t
1289 {
1290   /* number of paths in the hash */
1291   unsigned count;
1292
1293   /* COUNT keys (paths) */
1294   const char **keys;
1295
1296   /* COUNT keys lengths (strlen of path) */
1297   apr_ssize_t *key_lengths;
1298
1299   /* COUNT entries, each giving the number of ranges for the key */
1300   int *range_counts;
1301
1302   /* all ranges in a single, concatenated buffer */
1303   svn_merge_range_t *ranges;
1304 } mergeinfo_data_t;
1305
1306 svn_error_t *
1307 svn_fs_fs__serialize_mergeinfo(void **data,
1308                                apr_size_t *data_len,
1309                                void *in,
1310                                apr_pool_t *pool)
1311 {
1312   svn_mergeinfo_t mergeinfo = in;
1313   mergeinfo_data_t merges;
1314   svn_temp_serializer__context_t *context;
1315   svn_stringbuf_t *serialized;
1316   apr_hash_index_t *hi;
1317   unsigned i;
1318   int k;
1319   apr_size_t range_count;
1320
1321   /* initialize our auxiliary data structure */
1322   merges.count = apr_hash_count(mergeinfo);
1323   merges.keys = apr_palloc(pool, sizeof(*merges.keys) * merges.count);
1324   merges.key_lengths = apr_palloc(pool, sizeof(*merges.key_lengths) *
1325                                         merges.count);
1326   merges.range_counts = apr_palloc(pool, sizeof(*merges.range_counts) *
1327                                          merges.count);
1328
1329   i = 0;
1330   range_count = 0;
1331   for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi), ++i)
1332     {
1333       svn_rangelist_t *ranges;
1334       apr_hash_this(hi, (const void**)&merges.keys[i],
1335                         &merges.key_lengths[i],
1336                         (void **)&ranges);
1337       merges.range_counts[i] = ranges->nelts;
1338       range_count += ranges->nelts;
1339     }
1340
1341   merges.ranges = apr_palloc(pool, sizeof(*merges.ranges) * range_count);
1342
1343   i = 0;
1344   for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1345     {
1346       svn_rangelist_t *ranges = apr_hash_this_val(hi);
1347       for (k = 0; k < ranges->nelts; ++k, ++i)
1348         merges.ranges[i] = *APR_ARRAY_IDX(ranges, k, svn_merge_range_t*);
1349     }
1350
1351   /* serialize it and all its elements */
1352   context = svn_temp_serializer__init(&merges,
1353                                       sizeof(merges),
1354                                       range_count * 30,
1355                                       pool);
1356
1357   /* keys array */
1358   svn_temp_serializer__push(context,
1359                             (const void * const *)&merges.keys,
1360                             merges.count * sizeof(*merges.keys));
1361
1362   for (i = 0; i < merges.count; ++i)
1363     svn_temp_serializer__add_string(context, &merges.keys[i]);
1364
1365   svn_temp_serializer__pop(context);
1366
1367   /* key lengths array */
1368   svn_temp_serializer__add_leaf(context,
1369                                 (const void * const *)&merges.key_lengths,
1370                                 merges.count * sizeof(*merges.key_lengths));
1371
1372   /* range counts array */
1373   svn_temp_serializer__add_leaf(context,
1374                                 (const void * const *)&merges.range_counts,
1375                                 merges.count * sizeof(*merges.range_counts));
1376
1377   /* ranges */
1378   svn_temp_serializer__add_leaf(context,
1379                                 (const void * const *)&merges.ranges,
1380                                 range_count * sizeof(*merges.ranges));
1381
1382   /* return the serialized result */
1383   serialized = svn_temp_serializer__get(context);
1384
1385   *data = serialized->data;
1386   *data_len = serialized->len;
1387
1388   return SVN_NO_ERROR;
1389 }
1390
1391 svn_error_t *
1392 svn_fs_fs__deserialize_mergeinfo(void **out,
1393                                  void *data,
1394                                  apr_size_t data_len,
1395                                  apr_pool_t *pool)
1396 {
1397   unsigned i;
1398   int k, n;
1399   mergeinfo_data_t *merges = (mergeinfo_data_t *)data;
1400   svn_mergeinfo_t mergeinfo;
1401
1402   /* de-serialize our auxiliary data structure */
1403   svn_temp_deserializer__resolve(merges, (void**)&merges->keys);
1404   svn_temp_deserializer__resolve(merges, (void**)&merges->key_lengths);
1405   svn_temp_deserializer__resolve(merges, (void**)&merges->range_counts);
1406   svn_temp_deserializer__resolve(merges, (void**)&merges->ranges);
1407
1408   /* de-serialize keys and add entries to the result */
1409   n = 0;
1410   mergeinfo = svn_hash__make(pool);
1411   for (i = 0; i < merges->count; ++i)
1412     {
1413       svn_rangelist_t *ranges = apr_array_make(pool,
1414                                                merges->range_counts[i],
1415                                                sizeof(svn_merge_range_t*));
1416       for (k = 0; k < merges->range_counts[i]; ++k, ++n)
1417         APR_ARRAY_PUSH(ranges, svn_merge_range_t*) = &merges->ranges[n];
1418
1419       svn_temp_deserializer__resolve(merges->keys,
1420                                      (void**)&merges->keys[i]);
1421       apr_hash_set(mergeinfo, merges->keys[i], merges->key_lengths[i], ranges);
1422     }
1423
1424   /* done */
1425   *out = mergeinfo;
1426
1427   return SVN_NO_ERROR;
1428 }
1429