Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_ra / compat.c
blobf593638a1bac25298be1412cc773562f25517f47
1 /*
2 * compat.c: compatibility compliance logic
4 * ====================================================================
5 * Copyright (c) 2004-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
21 /*** Includes. ***/
22 #include <apr_pools.h>
23 #include <assert.h>
25 #include "svn_error.h"
26 #include "svn_pools.h"
27 #include "svn_sorts.h"
28 #include "svn_path.h"
29 #include "svn_ra.h"
30 #include "svn_io.h"
31 #include "svn_compat.h"
32 #include "ra_loader.h"
33 #include "svn_private_config.h"
37 /* This is just like svn_sort_compare_revisions, save that it sorts
38 the revisions in *ascending* order. */
39 static int
40 compare_revisions(const void *a, const void *b)
42 svn_revnum_t a_rev = *(const svn_revnum_t *)a;
43 svn_revnum_t b_rev = *(const svn_revnum_t *)b;
44 if (a_rev == b_rev)
45 return 0;
46 return a_rev < b_rev ? -1 : 1;
49 /* Given the CHANGED_PATHS and REVISION from an instance of a
50 svn_log_message_receiver_t function, determine at which location
51 PATH may be expected in the next log message, and set *PREV_PATH_P
52 to that value. KIND is the node kind of PATH. Set *ACTION_P to a
53 character describing the change that caused this revision (as
54 listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
55 revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
56 copied. ACTION_P and COPYFROM_REV_P may be NULL, in which case
57 they are not used. Perform all allocations in POOL.
59 This is useful for tracking the various changes in location a
60 particular resource has undergone when performing an RA->get_logs()
61 operation on that resource.
63 static svn_error_t *
64 prev_log_path(const char **prev_path_p,
65 char *action_p,
66 svn_revnum_t *copyfrom_rev_p,
67 apr_hash_t *changed_paths,
68 const char *path,
69 svn_node_kind_t kind,
70 svn_revnum_t revision,
71 apr_pool_t *pool)
73 svn_log_changed_path_t *change;
74 const char *prev_path = NULL;
76 /* It's impossible to find the predecessor path of a NULL path. */
77 assert(path);
79 /* Initialize our return values for the action and copyfrom_rev in
80 case we have an unhandled case later on. */
81 if (action_p)
82 *action_p = 'M';
83 if (copyfrom_rev_p)
84 *copyfrom_rev_p = SVN_INVALID_REVNUM;
86 if (changed_paths)
88 /* See if PATH was explicitly changed in this revision. */
89 change = apr_hash_get(changed_paths, path, APR_HASH_KEY_STRING);
90 if (change)
92 /* If PATH was not newly added in this revision, then it may or may
93 not have also been part of a moved subtree. In this case, set a
94 default previous path, but still look through the parents of this
95 path for a possible copy event. */
96 if (change->action != 'A' && change->action != 'R')
98 prev_path = path;
100 else
102 /* PATH is new in this revision. This means it cannot have been
103 part of a copied subtree. */
104 if (change->copyfrom_path)
105 prev_path = apr_pstrdup(pool, change->copyfrom_path);
106 else
107 prev_path = NULL;
109 *prev_path_p = prev_path;
110 if (action_p)
111 *action_p = change->action;
112 if (copyfrom_rev_p)
113 *copyfrom_rev_p = change->copyfrom_rev;
114 return SVN_NO_ERROR;
118 if (apr_hash_count(changed_paths))
120 /* The path was not explicitly changed in this revision. The
121 fact that we're hearing about this revision implies, then,
122 that the path was a child of some copied directory. We need
123 to find that directory, and effectively "re-base" our path on
124 that directory's copyfrom_path. */
125 int i;
126 apr_array_header_t *paths;
128 /* Build a sorted list of the changed paths. */
129 paths = svn_sort__hash(changed_paths,
130 svn_sort_compare_items_as_paths, pool);
132 /* Now, walk the list of paths backwards, looking a parent of
133 our path that has copyfrom information. */
134 for (i = paths->nelts; i > 0; i--)
136 svn_sort__item_t item = APR_ARRAY_IDX(paths,
137 i - 1, svn_sort__item_t);
138 const char *ch_path = item.key;
139 int len = strlen(ch_path);
141 /* See if our path is the child of this change path. If
142 not, keep looking. */
143 if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
144 continue;
146 /* Okay, our path *is* a child of this change path. If
147 this change was copied, we just need to apply the
148 portion of our path that is relative to this change's
149 path, to the change's copyfrom path. Otherwise, this
150 change isn't really interesting to us, and our search
151 continues. */
152 change = apr_hash_get(changed_paths, ch_path, len);
153 if (change->copyfrom_path)
155 if (action_p)
156 *action_p = change->action;
157 if (copyfrom_rev_p)
158 *copyfrom_rev_p = change->copyfrom_rev;
159 prev_path = svn_path_join(change->copyfrom_path,
160 path + len + 1, pool);
161 break;
167 /* If we didn't find what we expected to find, return an error.
168 (Because directories bubble-up, we get a bunch of logs we might
169 not want. Be forgiving in that case.) */
170 if (! prev_path)
172 if (kind == svn_node_dir)
173 prev_path = apr_pstrdup(pool, path);
174 else
175 return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
176 _("Missing changed-path information for "
177 "'%s' in revision %ld"),
178 svn_path_local_style(path, pool), revision);
181 *prev_path_p = prev_path;
182 return SVN_NO_ERROR;
187 /*** Fallback implementation of svn_ra_get_locations(). ***/
190 /* ### This is to support 1.0 servers. */
191 struct log_receiver_baton
193 /* The kind of the path we're tracing. */
194 svn_node_kind_t kind;
196 /* The path at which we are trying to find our versioned resource in
197 the log output. */
198 const char *last_path;
200 /* Input revisions and output hash; the whole point of this little game. */
201 svn_revnum_t peg_revision;
202 apr_array_header_t *location_revisions;
203 const char *peg_path;
204 apr_hash_t *locations;
206 /* A pool from which to allocate stuff stored in this baton. */
207 apr_pool_t *pool;
211 /* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
212 As input, takes log_receiver_baton (defined above) and attempts to
213 "fill in" locations in the baton over the course of many
214 iterations. */
215 static svn_error_t *
216 log_receiver(void *baton,
217 svn_log_entry_t *log_entry,
218 apr_pool_t *pool)
220 struct log_receiver_baton *lrb = baton;
221 apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
222 const char *current_path = lrb->last_path;
223 const char *prev_path;
225 /* No paths were changed in this revision. Nothing to do. */
226 if (! log_entry->changed_paths)
227 return SVN_NO_ERROR;
229 /* If we've run off the end of the path's history, there's nothing
230 to do. (This should never happen with a properly functioning
231 server, since we'd get no more log messages after the one where
232 path was created. But a malfunctioning server shouldn't cause us
233 to trigger an assertion failure.) */
234 if (! current_path)
235 return SVN_NO_ERROR;
237 /* If we haven't found our peg path yet, and we are now looking at a
238 revision equal to or older than the peg revision, then our
239 "current" path is our peg path. */
240 if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
241 lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
243 /* Determine the paths for any of the revisions for which we haven't
244 gotten paths already. */
245 while (lrb->location_revisions->nelts)
247 svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
248 lrb->location_revisions->nelts - 1,
249 svn_revnum_t);
250 if (log_entry->revision <= next)
252 apr_hash_set(lrb->locations,
253 apr_pmemdup(hash_pool, &next, sizeof(next)),
254 sizeof(next),
255 apr_pstrdup(hash_pool, current_path));
256 apr_array_pop(lrb->location_revisions);
258 else
259 break;
262 /* Figure out at which repository path our object of interest lived
263 in the previous revision. */
264 SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths,
265 current_path, lrb->kind, log_entry->revision, pool));
267 /* Squirrel away our "next place to look" path (suffer the strcmp
268 hit to save on allocations). */
269 if (! prev_path)
270 lrb->last_path = NULL;
271 else if (strcmp(prev_path, current_path) != 0)
272 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
274 return SVN_NO_ERROR;
278 svn_error_t *
279 svn_ra__locations_from_log(svn_ra_session_t *session,
280 apr_hash_t **locations_p,
281 const char *path,
282 svn_revnum_t peg_revision,
283 apr_array_header_t *location_revisions,
284 apr_pool_t *pool)
286 apr_hash_t *locations = apr_hash_make(pool);
287 struct log_receiver_baton lrb = { 0 };
288 apr_array_header_t *targets;
289 svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
290 svn_node_kind_t kind;
291 const char *root_url, *url, *rel_path;
293 /* Fetch the repository root URL and relative path. */
294 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
295 SVN_ERR(svn_ra_get_session_url(session, &url, pool));
296 url = svn_path_join(url, path, pool);
297 rel_path = svn_path_uri_decode(url + strlen(root_url), pool);
299 /* Sanity check: verify that the peg-object exists in repos. */
300 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
301 if (kind == svn_node_none)
302 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
303 _("Path '%s' doesn't exist in revision %ld"),
304 rel_path, peg_revision);
306 /* Easy out: no location revisions. */
307 if (! location_revisions->nelts)
309 *locations_p = locations;
310 return SVN_NO_ERROR;
313 /* Figure out the youngest and oldest revs (amongst the set of
314 requested revisions + the peg revision) so we can avoid
315 unnecessary log parsing. */
316 qsort(location_revisions->elts, location_revisions->nelts,
317 location_revisions->elt_size, compare_revisions);
318 oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t);
319 youngest_requested = APR_ARRAY_IDX(location_revisions,
320 location_revisions->nelts - 1,
321 svn_revnum_t);
322 youngest = peg_revision;
323 youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
324 youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
325 oldest = peg_revision;
326 oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
327 oldest = (youngest_requested < oldest) ? youngest_requested : oldest;
329 /* Populate most of our log receiver baton structure. */
330 lrb.kind = kind;
331 lrb.last_path = rel_path;
332 lrb.location_revisions = apr_array_copy(pool, location_revisions);
333 lrb.peg_revision = peg_revision;
334 lrb.peg_path = NULL;
335 lrb.locations = locations;
336 lrb.pool = pool;
338 /* Let the RA layer drive our log information handler, which will do
339 the work of finding the actual locations for our resource.
340 Notice that we always run on the youngest rev of the 3 inputs. */
341 targets = apr_array_make(pool, 1, sizeof(const char *));
342 APR_ARRAY_PUSH(targets, const char *) = path;
343 SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
344 TRUE, FALSE, FALSE,
345 apr_array_make(pool, 0, sizeof(const char *)),
346 log_receiver, &lrb, pool));
348 /* If the received log information did not cover any of the
349 requested revisions, use the last known path. (This normally
350 just means that ABS_PATH was not modified between the requested
351 revision and OLDEST. If the file was created at some point after
352 OLDEST, then lrb.last_path should be NULL.) */
353 if (! lrb.peg_path)
354 lrb.peg_path = lrb.last_path;
355 if (lrb.last_path)
357 int i;
358 for (i = 0; i < location_revisions->nelts; i++)
360 svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i,
361 svn_revnum_t);
362 if (! apr_hash_get(locations, &rev, sizeof(rev)))
363 apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
364 sizeof(rev), apr_pstrdup(pool, lrb.last_path));
368 /* Check that we got the peg path. */
369 if (! lrb.peg_path)
370 return svn_error_createf
371 (APR_EGENERAL, NULL,
372 _("Unable to find repository location for '%s' in revision %ld"),
373 rel_path, peg_revision);
375 /* Sanity check: make sure that our calculated peg path is the same
376 as what we expected it to be. */
377 if (strcmp(rel_path, lrb.peg_path) != 0)
378 return svn_error_createf
379 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
380 _("'%s' in revision %ld is an unrelated object"),
381 rel_path, youngest);
383 *locations_p = locations;
384 return SVN_NO_ERROR;
390 /*** Fallback implementation of svn_ra_get_location_segments(). ***/
392 struct gls_log_receiver_baton {
393 /* The kind of the path we're tracing. */
394 svn_node_kind_t kind;
396 /* Are we finished (and just listening to log entries because our
397 caller won't shut up?). */
398 svn_boolean_t done;
400 /* The path at which we are trying to find our versioned resource in
401 the log output. */
402 const char *last_path;
404 /* Input data. */
405 svn_revnum_t start_rev;
407 /* Output intermediate state and callback/baton. */
408 svn_revnum_t range_end;
409 svn_location_segment_receiver_t receiver;
410 void *receiver_baton;
412 /* A pool from which to allocate stuff stored in this baton. */
413 apr_pool_t *pool;
416 /* Build a node location segment object from PATH, RANGE_START, and
417 RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
418 static svn_error_t *
419 maybe_crop_and_send_segment(const char *path,
420 svn_revnum_t start_rev,
421 svn_revnum_t range_start,
422 svn_revnum_t range_end,
423 svn_location_segment_receiver_t receiver,
424 void *receiver_baton,
425 apr_pool_t *pool)
427 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
428 segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
429 segment->range_start = range_start;
430 segment->range_end = range_end;
431 if (segment->range_start <= start_rev)
433 if (segment->range_end > start_rev)
434 segment->range_end = start_rev;
435 return receiver(segment, receiver_baton, pool);
437 return SVN_NO_ERROR;
440 static svn_error_t *
441 gls_log_receiver(void *baton,
442 svn_log_entry_t *log_entry,
443 apr_pool_t *pool)
445 struct gls_log_receiver_baton *lrb = baton;
446 const char *current_path = lrb->last_path;
447 const char *prev_path;
448 svn_revnum_t copyfrom_rev;
450 /* If we're done, ignore this invocation. */
451 if (lrb->done)
452 return SVN_NO_ERROR;
454 /* Figure out at which repository path our object of interest lived
455 in the previous revision, and if its current location is the
456 result of copy since then. */
457 SVN_ERR(prev_log_path(&prev_path, NULL, &copyfrom_rev,
458 log_entry->changed_paths, current_path,
459 lrb->kind, log_entry->revision, pool));
461 /* If we've run off the end of the path's history, we need to report
462 our final segment (and then, we're done). */
463 if (! prev_path)
465 lrb->done = TRUE;
466 return maybe_crop_and_send_segment(current_path, lrb->start_rev,
467 log_entry->revision, lrb->range_end,
468 lrb->receiver, lrb->receiver_baton,
469 pool);
472 /* If there was a copy operation of interest... */
473 if (SVN_IS_VALID_REVNUM(copyfrom_rev))
475 /* ...then report the segment between this revision and the
476 last-reported revision. */
477 SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
478 log_entry->revision, lrb->range_end,
479 lrb->receiver, lrb->receiver_baton,
480 pool));
481 lrb->range_end = log_entry->revision - 1;
483 /* And if there was a revision gap, we need to report that, too. */
484 if (log_entry->revision - copyfrom_rev > 1)
486 SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
487 copyfrom_rev + 1, lrb->range_end,
488 lrb->receiver,
489 lrb->receiver_baton, pool));
490 lrb->range_end = copyfrom_rev;
493 /* Update our state variables. */
494 lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
497 return SVN_NO_ERROR;
501 svn_error_t *
502 svn_ra__location_segments_from_log(svn_ra_session_t *session,
503 const char *path,
504 svn_revnum_t peg_revision,
505 svn_revnum_t start_rev,
506 svn_revnum_t end_rev,
507 svn_location_segment_receiver_t receiver,
508 void *receiver_baton,
509 apr_pool_t *pool)
511 struct gls_log_receiver_baton lrb = { 0 };
512 apr_array_header_t *targets;
513 svn_node_kind_t kind;
514 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
515 const char *root_url, *url, *rel_path;
517 /* Fetch the repository root URL and relative path. */
518 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
519 SVN_ERR(svn_ra_get_session_url(session, &url, pool));
520 url = svn_path_join(url, path, pool);
521 rel_path = svn_path_uri_decode(url + strlen(root_url), pool);
523 /* If PEG_REVISION is invalid, it means HEAD. If START_REV is
524 invalid, it means HEAD. If END_REV is SVN_INVALID_REVNUM, we'll
525 use 0. */
526 if (! SVN_IS_VALID_REVNUM(peg_revision))
528 SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
529 peg_revision = youngest_rev;
531 if (! SVN_IS_VALID_REVNUM(start_rev))
533 if (SVN_IS_VALID_REVNUM(youngest_rev))
534 start_rev = youngest_rev;
535 else
536 SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
538 if (! SVN_IS_VALID_REVNUM(end_rev))
540 end_rev = 0;
543 /* The API demands a certain ordering of our revision inputs. Enforce it. */
544 assert((peg_revision >= start_rev) && (start_rev >= end_rev));
546 /* Sanity check: verify that the peg-object exists in repos. */
547 SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
548 if (kind == svn_node_none)
549 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
550 _("Path '%s' doesn't exist in revision %ld"),
551 rel_path, start_rev);
553 /* Populate most of our log receiver baton structure. */
554 lrb.kind = kind;
555 lrb.last_path = rel_path;
556 lrb.done = FALSE;
557 lrb.start_rev = start_rev;
558 lrb.range_end = start_rev;
559 lrb.receiver = receiver;
560 lrb.receiver_baton = receiver_baton;
561 lrb.pool = pool;
563 /* Let the RA layer drive our log information handler, which will do
564 the work of finding the actual locations for our resource.
565 Notice that we always run on the youngest rev of the 3 inputs. */
566 targets = apr_array_make(pool, 1, sizeof(const char *));
567 APR_ARRAY_PUSH(targets, const char *) = path;
568 SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
569 TRUE, FALSE, FALSE,
570 apr_array_make(pool, 0, sizeof(const char *)),
571 gls_log_receiver, &lrb, pool));
573 /* If we didn't finish, we need to do so with a final segment send. */
574 if (! lrb.done)
575 SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
576 end_rev, lrb.range_end,
577 receiver, receiver_baton, pool));
579 return SVN_NO_ERROR;
584 /*** Fallback implementation of svn_ra_get_file_revs(). ***/
586 /* The metadata associated with a particular revision. */
587 struct rev
589 svn_revnum_t revision; /* the revision number */
590 const char *path; /* the absolute repository path */
591 apr_hash_t *props; /* the revprops for this revision */
592 struct rev *next; /* the next revision */
595 /* File revs log message baton. */
596 struct fr_log_message_baton {
597 const char *path; /* The path to be processed */
598 struct rev *eldest; /* The eldest revision processed */
599 char action; /* The action associated with the eldest */
600 svn_revnum_t copyrev; /* The revision the eldest was copied from */
601 apr_pool_t *pool;
604 /* Callback for log messages: implements svn_log_entry_receiver_t and
605 accumulates revision metadata into a chronologically ordered list stored in
606 the baton. */
607 static svn_error_t *
608 fr_log_message_receiver(void *baton,
609 svn_log_entry_t *log_entry,
610 apr_pool_t *pool)
612 struct fr_log_message_baton *lmb = baton;
613 struct rev *rev;
614 apr_hash_index_t *hi;
616 rev = apr_palloc(lmb->pool, sizeof(*rev));
617 rev->revision = log_entry->revision;
618 rev->path = lmb->path;
619 rev->next = lmb->eldest;
620 lmb->eldest = rev;
622 /* Duplicate log_entry revprops into rev->props */
623 rev->props = apr_hash_make(lmb->pool);
624 for (hi = apr_hash_first(pool, log_entry->revprops); hi;
625 hi = apr_hash_next(hi))
627 svn_string_t *val;
628 const char *key;
630 apr_hash_this(hi, (const void **)&key, NULL, (void **)&val);
631 apr_hash_set(rev->props, apr_pstrdup(lmb->pool, key), APR_HASH_KEY_STRING,
632 svn_string_dup(val, lmb->pool));
635 return prev_log_path(&lmb->path, &lmb->action,
636 &lmb->copyrev, log_entry->changed_paths,
637 lmb->path, svn_node_file, log_entry->revision,
638 lmb->pool);
641 svn_error_t *
642 svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
643 const char *path,
644 svn_revnum_t start,
645 svn_revnum_t end,
646 svn_file_rev_handler_t handler,
647 void *handler_baton,
648 apr_pool_t *pool)
650 svn_node_kind_t kind;
651 const char *repos_url;
652 const char *session_url;
653 const char *tmp;
654 char *repos_abs_path;
655 apr_array_header_t *condensed_targets;
656 struct fr_log_message_baton lmb;
657 struct rev *rev;
658 apr_hash_t *last_props;
659 const char *last_path;
660 svn_stream_t *last_stream;
661 apr_pool_t *currpool, *lastpool;
663 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
664 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
666 /* Create the initial path, using the repos_url and session_url */
667 tmp = svn_path_is_child(repos_url, session_url, pool);
668 repos_abs_path = apr_palloc(pool, strlen(tmp) + 1);
669 repos_abs_path[0] = '/';
670 memcpy(repos_abs_path + 1, tmp, strlen(tmp));
672 /* Check to make sure we're dealing with a file. */
673 SVN_ERR(svn_ra_check_path(ra_session, "", end, &kind, pool));
675 if (kind == svn_node_dir)
676 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
677 _("'%s' is not a file"), repos_abs_path);
679 condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
680 APR_ARRAY_PUSH(condensed_targets, const char *) = "";
682 lmb.path = svn_path_uri_decode(repos_abs_path, pool);
683 lmb.eldest = NULL;
684 lmb.pool = pool;
686 /* Accumulate revision metadata by walking the revisions
687 backwards; this allows us to follow moves/copies
688 correctly. */
689 SVN_ERR(svn_ra_get_log2(ra_session,
690 condensed_targets,
691 end, start, 0, /* no limit */
692 TRUE, FALSE, FALSE,
693 NULL, fr_log_message_receiver, &lmb,
694 pool));
696 /* Reparent the session while we go back through the history. */
697 SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));
699 currpool = svn_pool_create(pool);
700 lastpool = svn_pool_create(pool);
702 /* We want the first txdelta to be against the empty file. */
703 last_props = apr_hash_make(lastpool);
704 last_path = NULL;
705 last_stream = svn_stream_empty(lastpool);
707 /* Walk the revision list in chronological order, downloading each fulltext,
708 diffing it with its predecessor, and calling the file_revs handler for
709 each one. Use two iteration pools rather than one, because the diff
710 routines need to look at a sliding window of revisions. Two pools gives
711 us a ring buffer of sorts. */
712 for (rev = lmb.eldest; rev; rev = rev->next)
714 const char *temp_path;
715 const char *temp_dir;
716 apr_pool_t *tmppool;
717 apr_hash_t *props;
718 apr_file_t *file;
719 svn_stream_t *stream;
720 apr_array_header_t *prop_diffs;
721 svn_txdelta_stream_t *delta_stream;
722 svn_txdelta_window_handler_t delta_handler = NULL;
723 void *delta_baton = NULL;
725 svn_pool_clear(currpool);
727 /* Get the contents of the file from the repository, and put them in
728 a temporary local file. */
729 SVN_ERR(svn_io_temp_dir(&temp_dir, currpool));
730 SVN_ERR(svn_io_open_unique_file2
731 (&file, &temp_path,
732 svn_path_join(temp_dir, "tmp", currpool), ".tmp",
733 svn_io_file_del_on_pool_cleanup, currpool));
734 stream = svn_stream_from_aprfile(file, currpool);
735 SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
736 stream, NULL, &props, currpool));
737 SVN_ERR(svn_stream_close(stream));
738 SVN_ERR(svn_io_file_close(file, currpool));
740 /* Open up a stream to the local file. */
741 SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
742 currpool));
743 stream = svn_stream_from_aprfile2(file, FALSE, currpool);
745 /* Calculate the property diff */
746 SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
748 /* Call the file_rev handler */
749 SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
750 FALSE, /* merged revision */
751 &delta_handler, &delta_baton, prop_diffs, lastpool));
753 /* Compute and send delta if client asked for it. */
754 if (delta_handler)
756 /* Get the content delta. */
757 svn_txdelta(&delta_stream, last_stream, stream, lastpool);
759 /* And send. */
760 SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
761 delta_baton, lastpool));
764 /* Switch the pools and data for the next iteration */
765 tmppool = currpool;
766 currpool = lastpool;
767 lastpool = tmppool;
769 svn_stream_close(last_stream);
770 last_stream = stream;
771 last_props = props;
774 svn_stream_close(last_stream);
775 svn_pool_destroy(currpool);
776 svn_pool_destroy(lastpool);
778 /* Reparent the session back to the original URL. */
779 return svn_ra_reparent(ra_session, session_url, pool);